changeset 2:b44434aaa767

Moving around the components. Made a big step in the right direction with the Builder and named joins being accessible.
author luka
date Wed, 18 Jun 2025 22:28:47 -0400
parents 56d9c64d64aa
children e107504fa22c
files publishable/resources/views/components/application-logo.blade.php publishable/resources/views/components/auth-session-status.blade.php publishable/resources/views/components/card.blade.php publishable/resources/views/components/danger-button.blade.php publishable/resources/views/components/dashboard-card.blade.php publishable/resources/views/components/dropdown-link.blade.php publishable/resources/views/components/dropdown.blade.php publishable/resources/views/components/filter/badge.blade.php publishable/resources/views/components/form/checkbox.blade.php publishable/resources/views/components/form/date.blade.php publishable/resources/views/components/form/select.blade.php publishable/resources/views/components/form/text.blade.php publishable/resources/views/components/form/textarea.blade.php publishable/resources/views/components/input-error.blade.php publishable/resources/views/components/input-label.blade.php publishable/resources/views/components/modal.blade.php publishable/resources/views/components/nav-link.blade.php publishable/resources/views/components/preview-footer.blade.php publishable/resources/views/components/preview-list-card.blade.php publishable/resources/views/components/primary-button.blade.php publishable/resources/views/components/responsive-nav-link.blade.php publishable/resources/views/components/secondary-button.blade.php publishable/resources/views/components/text-input.blade.php publishable/vite.config.js resources/views/components/application-logo.blade.php resources/views/components/auth-session-status.blade.php resources/views/components/card.blade.php resources/views/components/danger-button.blade.php resources/views/components/dashboard-card.blade.php resources/views/components/dropdown-link.blade.php resources/views/components/dropdown.blade.php resources/views/components/filter/badge.blade.php resources/views/components/form/checkbox.blade.php resources/views/components/form/date.blade.php resources/views/components/form/select.blade.php resources/views/components/form/text.blade.php resources/views/components/form/textarea.blade.php resources/views/components/input-error.blade.php resources/views/components/input-label.blade.php resources/views/components/modal.blade.php resources/views/components/nav-link.blade.php resources/views/components/preview-footer.blade.php resources/views/components/preview-list-card.blade.php resources/views/components/primary-button.blade.php resources/views/components/responsive-nav-link.blade.php resources/views/components/secondary-button.blade.php resources/views/components/text-input.blade.php src/Database/Builder.php src/Database/MarkdownParser.php src/Database/MyBlueprint.php src/Database/MySchema.php src/Database/SchemaDrawer.php src/FrameworkServiceProvider.php src/Http/Controllers/BaseController.php src/Models/BaseModel.php src/Traits/Filterable.php
diffstat 56 files changed, 709 insertions(+), 327 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/application-logo.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,1 @@
+<img src="{{ asset(config('app.logo', null)) }}" class="size-[64px]"></img>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/auth-session-status.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,7 @@
+@props(['status'])
+
+@if ($status)
+    <div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600']) }}>
+        {{ $status }}
+    </div>
+@endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/card.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,23 @@
+@props([
+    'title' => null, // e.g. 'Upcoming Milestones'
+    'icon' => null, // SVG (pass as a Blade include or html)
+    'empty' => 'Nothing to show.',
+    'footer' => null, // Optional (e.g. a "View All" link)
+    'headerButton' => null, //button or link for the right side of the header
+])
+
+<div class="bg-white p-4 rounded-lg shadow min-h-[200px]">
+    <div class="flex justify-between items-center">
+        <h4 class="font-semibold text-gray-800 mb-3 flex items-center">
+            {!! $icon ?? '' !!}
+            <span class="ml-2">{{ $title }}</span>
+        </h4>
+        {!! $headerButton !!}
+    </div>
+    {{ $slot }}
+    @if ($footer)
+        <div class="text-right mt-3">
+            {!! $footer !!}
+        </div>
+    @endif
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/danger-button.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,4 @@
+<button
+    {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
+    {{ $slot }}
+</button>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/dashboard-card.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,33 @@
+@props([
+    'icon', // SVG icon as a Blade include or HTML
+    'title',
+    'value',
+    'subtitle' => '',
+    'color' => 'blue', // For icon background
+])
+
+@php
+    $bgColor =
+        [
+            'green' => 'bg-green-100 text-green-600',
+            'blue' => 'bg-blue-100 text-blue-600',
+            'purple' => 'bg-purple-100 text-purple-600',
+            'yellow' => 'bg-yellow-100 text-yellow-600',
+            'gray' => 'bg-gray-100 text-gray-600',
+        ][$color] ?? 'bg-blue-100 text-blue-600';
+@endphp
+
+<div class="flex items-center bg-white rounded-lg shadow p-4 min-w-[170px]">
+    <div class="flex-shrink-0">
+        <span class="inline-flex items-center justify-center h-12 w-12 rounded-full {{ $bgColor }}">
+            {!! $icon !!}
+        </span>
+    </div>
+    <div class="ml-4">
+        <div class="text-lg font-semibold text-gray-900">{{ $value }}</div>
+        <div class="text-gray-500">{{ $title }}</div>
+        @if ($subtitle)
+            <div class="text-xs text-gray-400 mt-1">{{ $subtitle }}</div>
+        @endif
+    </div>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/dropdown-link.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,2 @@
+<a
+    {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }}</a>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/dropdown.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,30 @@
+@props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white'])
+
+@php
+    $alignmentClasses = match ($align) {
+        'left' => 'ltr:origin-top-left rtl:origin-top-right start-0',
+        'top' => 'origin-top',
+        default => 'ltr:origin-top-right rtl:origin-top-left end-0',
+    };
+
+    $width = match ($width) {
+        '48' => 'w-48',
+        default => $width,
+    };
+@endphp
+
+<div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
+    <div @click="open = ! open">
+        {{ $trigger }}
+    </div>
+
+    <div x-show="open" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 scale-95"
+        x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-75"
+        x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
+        class="absolute z-50 mt-2 {{ $width }} rounded-md shadow-lg {{ $alignmentClasses }}"
+        style="display: none;" @click="open = false">
+        <div class="rounded-md ring-1 ring-black ring-opacity-5 {{ $contentClasses }}">
+            {{ $content }}
+        </div>
+    </div>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/filter/badge.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,13 @@
+<span id="badge-dismiss-dark"
+    class="inline-flex items-center px-2 py-1 me-2 text-sm font-medium text-gray-800 bg-gray-100 rounded-sm dark:bg-gray-700 dark:text-gray-300">
+    {{ $name }}
+    <button type="button"
+        class="inline-flex items-center p-1 ms-2 text-sm text-gray-400 bg-transparent rounded-xs hover:bg-gray-200 hover:text-gray-900 dark:hover:bg-gray-600 dark:hover:text-gray-300"
+        data-dismiss-target="#badge-dismiss-dark" aria-label="Remove">
+        <svg class="w-2 h-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
+            <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
+                d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
+        </svg>
+        <span class="sr-only">Remove Filter</span>
+    </button>
+</span>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/form/checkbox.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,11 @@
+@props(['name', 'label', 'checked' => false])
+
+<div class="mb-4 flex items-center">
+    <input type="checkbox" id="{{ $name }}" name="{{ $name }}" value="1"
+        {{ old($name, $checked) ? 'checked' : '' }}
+        class="rounded border-gray-300 text-blue-600 shadow-sm focus:ring-blue-200">
+    <label for="{{ $name }}" class="ml-2 text-gray-700">{{ $label }}</label>
+    @error($name)
+        <span class="text-red-600 text-sm">{{ $message }}</span>
+    @enderror
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/form/date.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,11 @@
+@props(['name', 'label', 'value' => '', 'required' => false])
+
+<div class="mb-4">
+    <label for="{{ $name }}" class="block text-gray-700">{{ $label }}</label>
+    <input type="date" name="{{ $name }}" id="{{ $name }}" value="{{ old($name, $value) }}"
+        @if ($required) required @endif
+        class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring focus:ring-blue-200">
+    @error($name)
+        <span class="text-red-600 text-sm">{{ $message }}</span>
+    @enderror
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/form/select.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,16 @@
+@props(['name', 'label', 'options' => [], 'value' => '', 'required' => false])
+<div class="mb-4">
+    <label for="{{ $name }}" class="block text-gray-700">{{ $label }}</label>
+    <select name="{{ $name }}" id="{{ $name }}" @if ($required) required @endif
+        class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring focus:ring-blue-200">
+        <option value="">Select...</option>
+        @foreach ($options as $optionValue => $optionLabel)
+            <option value="{{ $optionValue }}" {{ old($name, $value) == $optionValue ? 'selected' : '' }}>
+                {{ $optionLabel }}
+            </option>
+        @endforeach
+    </select>
+    @error($name)
+        <span class="text-red-600 text-sm">{{ $message }}</span>
+    @enderror
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/form/text.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,11 @@
+@props(['name', 'label', 'value' => '', 'required' => false])
+
+<div class="mb-4">
+    <label for="{{ $name }}" class="block text-gray-700">{{ $label }}</label>
+    <input type="text" name="{{ $name }}" id="{{ $name }}" value="{{ old($name, $value) }}"
+        @if ($required) required @endif
+        class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring focus:ring-blue-200">
+    @error($name)
+        <span class="text-red-600 text-sm">{{ $message }}</span>
+    @enderror
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/form/textarea.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,22 @@
+@props(['name', 'label', 'value' => '', 'required' => false])
+
+<div class="mb-4">
+    <label for="{{ $name }}" class="block text-gray-700 font-medium mb-1">
+        {{ $label }}
+        @if($required)
+            <span class="text-red-500">*</span>
+        @endif
+    </label>
+
+    <textarea 
+        name="{{ $name }}" 
+        id="{{ $name }}" 
+        @if ($required) required @endif
+        class="markdown-editor mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring focus:ring-blue-200"
+    >{{ old($name, $value) }}</textarea>
+
+    @error($name)
+        <span class="text-red-600 text-sm">{{ $message }}</span>
+    @enderror
+</div>
+@include('includes.easymde')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/input-error.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,9 @@
+@props(['messages'])
+
+@if ($messages)
+    <ul {{ $attributes->merge(['class' => 'text-sm text-red-600 space-y-1']) }}>
+        @foreach ((array) $messages as $message)
+            <li>{{ $message }}</li>
+        @endforeach
+    </ul>
+@endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/input-label.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,5 @@
+@props(['value'])
+
+<label {{ $attributes->merge(['class' => 'block font-medium text-sm text-gray-700']) }}>
+    {{ $value ?? $slot }}
+</label>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/modal.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,57 @@
+@props(['name', 'show' => false, 'maxWidth' => '2xl'])
+
+@php
+    $maxWidth = [
+        'sm' => 'sm:max-w-sm',
+        'md' => 'sm:max-w-md',
+        'lg' => 'sm:max-w-lg',
+        'xl' => 'sm:max-w-xl',
+        '2xl' => 'sm:max-w-2xl',
+    ][$maxWidth];
+@endphp
+
+<div x-data="{
+    show: @js($show),
+    focusables() {
+        // All focusable element types...
+        let selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
+        return [...$el.querySelectorAll(selector)]
+            // All non-disabled elements...
+            .filter(el => !el.hasAttribute('disabled'))
+    },
+    firstFocusable() { return this.focusables()[0] },
+    lastFocusable() { return this.focusables().slice(-1)[0] },
+    nextFocusable() { return this.focusables()[this.nextFocusableIndex()] || this.firstFocusable() },
+    prevFocusable() { return this.focusables()[this.prevFocusableIndex()] || this.lastFocusable() },
+    nextFocusableIndex() { return (this.focusables().indexOf(document.activeElement) + 1) % (this.focusables().length + 1) },
+    prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) - 1 },
+}" x-init="$watch('show', value => {
+    if (value) {
+        document.body.classList.add('overflow-y-hidden');
+        {{ $attributes->has('focusable') ? 'setTimeout(() => firstFocusable().focus(), 100)' : '' }}
+    } else {
+        document.body.classList.remove('overflow-y-hidden');
+    }
+})"
+    x-on:open-modal.window="$event.detail == '{{ $name }}' ? show = true : null"
+    x-on:close-modal.window="$event.detail == '{{ $name }}' ? show = false : null" x-on:close.stop="show = false"
+    x-on:keydown.escape.window="show = false" x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
+    x-on:keydown.shift.tab.prevent="prevFocusable().focus()" x-show="show"
+    class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50" style="display: {{ $show ? 'block' : 'none' }};">
+    <div x-show="show" class="fixed inset-0 transform transition-all" x-on:click="show = false"
+        x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0"
+        x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200"
+        x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0">
+        <div class="absolute inset-0 bg-gray-500 opacity-75"></div>
+    </div>
+
+    <div x-show="show"
+        class="mb-6 bg-white rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full {{ $maxWidth }} sm:mx-auto"
+        x-transition:enter="ease-out duration-300"
+        x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
+        x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200"
+        x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
+        x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
+        {{ $slot }}
+    </div>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/nav-link.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,12 @@
+@props(['active'])
+
+@php
+    $classes =
+        $active ?? false
+            ? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out'
+            : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out';
+@endphp
+
+<a {{ $attributes->merge(['class' => $classes]) }}>
+    {{ $slot }}
+</a>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/preview-footer.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,1 @@
+<a href="{{ $route }}" class="text-blue-600 text-sm hover:underline">{{ $label }}</a>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/preview-list-card.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,22 @@
+@props([
+    'title', // e.g. 'Upcoming Milestones'
+    'icon', // SVG (pass as a Blade include or html)
+    'items', // Collection of items to show
+    'itemView', // Partial for rendering each item
+    'viewData' => [], // Additional data to pass to the partial
+    'empty' => 'Nothing to show.',
+    'footer' => null, // Optional (e.g. a "View All" link)
+    'headerButton' => null, // Optional (e.g. a "Add" link)
+])
+
+<x-card :icon="$icon" :title="$title" :headerButton="$headerButton" :footer="$footer">
+    @if ($items->count())
+        <ul class="divide-y divide-gray-100">
+            @foreach ($items as $item)
+                @include($itemView, array_merge(['item' => $item], $viewData ?? []))
+            @endforeach
+        </ul>
+    @else
+        <div class="text-gray-400 text-sm">{{ $empty }}</div>
+    @endif
+</x-card>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/primary-button.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,4 @@
+<button
+    {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
+    {{ $slot }}
+</button>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/responsive-nav-link.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,12 @@
+@props(['active'])
+
+@php
+    $classes =
+        $active ?? false
+            ? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 text-start text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out'
+            : 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out';
+@endphp
+
+<a {{ $attributes->merge(['class' => $classes]) }}>
+    {{ $slot }}
+</a>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/secondary-button.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,4 @@
+<button
+    {{ $attributes->merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 transition ease-in-out duration-150']) }}>
+    {{ $slot }}
+</button>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/resources/views/components/text-input.blade.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,4 @@
+@props(['disabled' => false])
+
+<input @disabled($disabled)
+    {{ $attributes->merge(['class' => 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm']) }}>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/publishable/vite.config.js	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,30 @@
+import { defineConfig } from "vite";
+import laravel from "laravel-vite-plugin";
+import prism from 'vite-plugin-prismjs';
+import fs from "fs";
+import path from "path";
+
+const pagesDir = "resources/js/pages";
+const pageScripts = fs
+    .readdirSync(pagesDir)
+    .map((file) => path.join(pagesDir, file));
+
+export default defineConfig({
+    plugins: [
+        laravel({
+            input: [
+                "resources/css/app.css",
+                "resources/js/app.js",
+                "resources/js/easymde.js",
+                ...pageScripts,
+            ],
+            refresh: true,
+        }),
+        prism({
+            languages: ["javascript", "css", "html", "typescript", "php", "sql", "bash", "sh"],
+            plugins: ["line-numbers"],
+            theme: "tomorrow",
+            css: true,
+        }),
+    ],
+});
--- a/resources/views/components/application-logo.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-<img src="{{ asset(config('app.logo', null)) }}" class="size-[64px]"></img>
--- a/resources/views/components/auth-session-status.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-@props(['status'])
-
-@if ($status)
-    <div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600']) }}>
-        {{ $status }}
-    </div>
-@endif
--- a/resources/views/components/card.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-@props([
-    'title' => null, // e.g. 'Upcoming Milestones'
-    'icon' => null, // SVG (pass as a Blade include or html)
-    'empty' => 'Nothing to show.',
-    'footer' => null, // Optional (e.g. a "View All" link)
-    'headerButton' => null, //button or link for the right side of the header
-])
-
-<div class="bg-white p-4 rounded-lg shadow min-h-[200px]">
-    <div class="flex justify-between items-center">
-        <h4 class="font-semibold text-gray-800 mb-3 flex items-center">
-            {!! $icon ?? '' !!}
-            <span class="ml-2">{{ $title }}</span>
-        </h4>
-        {!! $headerButton !!}
-    </div>
-    {{ $slot }}
-    @if ($footer)
-        <div class="text-right mt-3">
-            {!! $footer !!}
-        </div>
-    @endif
-</div>
--- a/resources/views/components/danger-button.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-<button
-    {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
-    {{ $slot }}
-</button>
--- a/resources/views/components/dashboard-card.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-@props([
-    'icon', // SVG icon as a Blade include or HTML
-    'title',
-    'value',
-    'subtitle' => '',
-    'color' => 'blue', // For icon background
-])
-
-@php
-    $bgColor =
-        [
-            'green' => 'bg-green-100 text-green-600',
-            'blue' => 'bg-blue-100 text-blue-600',
-            'purple' => 'bg-purple-100 text-purple-600',
-            'yellow' => 'bg-yellow-100 text-yellow-600',
-            'gray' => 'bg-gray-100 text-gray-600',
-        ][$color] ?? 'bg-blue-100 text-blue-600';
-@endphp
-
-<div class="flex items-center bg-white rounded-lg shadow p-4 min-w-[170px]">
-    <div class="flex-shrink-0">
-        <span class="inline-flex items-center justify-center h-12 w-12 rounded-full {{ $bgColor }}">
-            {!! $icon !!}
-        </span>
-    </div>
-    <div class="ml-4">
-        <div class="text-lg font-semibold text-gray-900">{{ $value }}</div>
-        <div class="text-gray-500">{{ $title }}</div>
-        @if ($subtitle)
-            <div class="text-xs text-gray-400 mt-1">{{ $subtitle }}</div>
-        @endif
-    </div>
-</div>
--- a/resources/views/components/dropdown-link.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-<a
-    {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }}</a>
--- a/resources/views/components/dropdown.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-@props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white'])
-
-@php
-    $alignmentClasses = match ($align) {
-        'left' => 'ltr:origin-top-left rtl:origin-top-right start-0',
-        'top' => 'origin-top',
-        default => 'ltr:origin-top-right rtl:origin-top-left end-0',
-    };
-
-    $width = match ($width) {
-        '48' => 'w-48',
-        default => $width,
-    };
-@endphp
-
-<div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
-    <div @click="open = ! open">
-        {{ $trigger }}
-    </div>
-
-    <div x-show="open" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 scale-95"
-        x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-75"
-        x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
-        class="absolute z-50 mt-2 {{ $width }} rounded-md shadow-lg {{ $alignmentClasses }}"
-        style="display: none;" @click="open = false">
-        <div class="rounded-md ring-1 ring-black ring-opacity-5 {{ $contentClasses }}">
-            {{ $content }}
-        </div>
-    </div>
-</div>
--- a/resources/views/components/filter/badge.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-<span id="badge-dismiss-dark"
-    class="inline-flex items-center px-2 py-1 me-2 text-sm font-medium text-gray-800 bg-gray-100 rounded-sm dark:bg-gray-700 dark:text-gray-300">
-    {{ $name }}
-    <button type="button"
-        class="inline-flex items-center p-1 ms-2 text-sm text-gray-400 bg-transparent rounded-xs hover:bg-gray-200 hover:text-gray-900 dark:hover:bg-gray-600 dark:hover:text-gray-300"
-        data-dismiss-target="#badge-dismiss-dark" aria-label="Remove">
-        <svg class="w-2 h-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
-            <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
-                d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
-        </svg>
-        <span class="sr-only">Remove Filter</span>
-    </button>
-</span>
--- a/resources/views/components/form/checkbox.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-@props(['name', 'label', 'checked' => false])
-
-<div class="mb-4 flex items-center">
-    <input type="checkbox" id="{{ $name }}" name="{{ $name }}" value="1"
-        {{ old($name, $checked) ? 'checked' : '' }}
-        class="rounded border-gray-300 text-blue-600 shadow-sm focus:ring-blue-200">
-    <label for="{{ $name }}" class="ml-2 text-gray-700">{{ $label }}</label>
-    @error($name)
-        <span class="text-red-600 text-sm">{{ $message }}</span>
-    @enderror
-</div>
--- a/resources/views/components/form/date.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-@props(['name', 'label', 'value' => '', 'required' => false])
-
-<div class="mb-4">
-    <label for="{{ $name }}" class="block text-gray-700">{{ $label }}</label>
-    <input type="date" name="{{ $name }}" id="{{ $name }}" value="{{ old($name, $value) }}"
-        @if ($required) required @endif
-        class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring focus:ring-blue-200">
-    @error($name)
-        <span class="text-red-600 text-sm">{{ $message }}</span>
-    @enderror
-</div>
--- a/resources/views/components/form/select.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-@props(['name', 'label', 'options' => [], 'value' => '', 'required' => false])
-<div class="mb-4">
-    <label for="{{ $name }}" class="block text-gray-700">{{ $label }}</label>
-    <select name="{{ $name }}" id="{{ $name }}" @if ($required) required @endif
-        class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring focus:ring-blue-200">
-        <option value="">Select...</option>
-        @foreach ($options as $optionValue => $optionLabel)
-            <option value="{{ $optionValue }}" {{ old($name, $value) == $optionValue ? 'selected' : '' }}>
-                {{ $optionLabel }}
-            </option>
-        @endforeach
-    </select>
-    @error($name)
-        <span class="text-red-600 text-sm">{{ $message }}</span>
-    @enderror
-</div>
--- a/resources/views/components/form/text.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-@props(['name', 'label', 'value' => '', 'required' => false])
-
-<div class="mb-4">
-    <label for="{{ $name }}" class="block text-gray-700">{{ $label }}</label>
-    <input type="text" name="{{ $name }}" id="{{ $name }}" value="{{ old($name, $value) }}"
-        @if ($required) required @endif
-        class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring focus:ring-blue-200">
-    @error($name)
-        <span class="text-red-600 text-sm">{{ $message }}</span>
-    @enderror
-</div>
--- a/resources/views/components/form/textarea.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-@props(['name', 'label', 'value' => '', 'required' => false])
-
-<div class="mb-4">
-    <label for="{{ $name }}" class="block text-gray-700 font-medium mb-1">
-        {{ $label }}
-        @if($required)
-            <span class="text-red-500">*</span>
-        @endif
-    </label>
-
-    <textarea 
-        name="{{ $name }}" 
-        id="{{ $name }}" 
-        @if ($required) required @endif
-        class="markdown-editor mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring focus:ring-blue-200"
-    >{{ old($name, $value) }}</textarea>
-
-    @error($name)
-        <span class="text-red-600 text-sm">{{ $message }}</span>
-    @enderror
-</div>
-@include('includes.easymde')
--- a/resources/views/components/input-error.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-@props(['messages'])
-
-@if ($messages)
-    <ul {{ $attributes->merge(['class' => 'text-sm text-red-600 space-y-1']) }}>
-        @foreach ((array) $messages as $message)
-            <li>{{ $message }}</li>
-        @endforeach
-    </ul>
-@endif
--- a/resources/views/components/input-label.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-@props(['value'])
-
-<label {{ $attributes->merge(['class' => 'block font-medium text-sm text-gray-700']) }}>
-    {{ $value ?? $slot }}
-</label>
--- a/resources/views/components/modal.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-@props(['name', 'show' => false, 'maxWidth' => '2xl'])
-
-@php
-    $maxWidth = [
-        'sm' => 'sm:max-w-sm',
-        'md' => 'sm:max-w-md',
-        'lg' => 'sm:max-w-lg',
-        'xl' => 'sm:max-w-xl',
-        '2xl' => 'sm:max-w-2xl',
-    ][$maxWidth];
-@endphp
-
-<div x-data="{
-    show: @js($show),
-    focusables() {
-        // All focusable element types...
-        let selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
-        return [...$el.querySelectorAll(selector)]
-            // All non-disabled elements...
-            .filter(el => !el.hasAttribute('disabled'))
-    },
-    firstFocusable() { return this.focusables()[0] },
-    lastFocusable() { return this.focusables().slice(-1)[0] },
-    nextFocusable() { return this.focusables()[this.nextFocusableIndex()] || this.firstFocusable() },
-    prevFocusable() { return this.focusables()[this.prevFocusableIndex()] || this.lastFocusable() },
-    nextFocusableIndex() { return (this.focusables().indexOf(document.activeElement) + 1) % (this.focusables().length + 1) },
-    prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) - 1 },
-}" x-init="$watch('show', value => {
-    if (value) {
-        document.body.classList.add('overflow-y-hidden');
-        {{ $attributes->has('focusable') ? 'setTimeout(() => firstFocusable().focus(), 100)' : '' }}
-    } else {
-        document.body.classList.remove('overflow-y-hidden');
-    }
-})"
-    x-on:open-modal.window="$event.detail == '{{ $name }}' ? show = true : null"
-    x-on:close-modal.window="$event.detail == '{{ $name }}' ? show = false : null" x-on:close.stop="show = false"
-    x-on:keydown.escape.window="show = false" x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
-    x-on:keydown.shift.tab.prevent="prevFocusable().focus()" x-show="show"
-    class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50" style="display: {{ $show ? 'block' : 'none' }};">
-    <div x-show="show" class="fixed inset-0 transform transition-all" x-on:click="show = false"
-        x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0"
-        x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200"
-        x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0">
-        <div class="absolute inset-0 bg-gray-500 opacity-75"></div>
-    </div>
-
-    <div x-show="show"
-        class="mb-6 bg-white rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full {{ $maxWidth }} sm:mx-auto"
-        x-transition:enter="ease-out duration-300"
-        x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
-        x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200"
-        x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
-        x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
-        {{ $slot }}
-    </div>
-</div>
--- a/resources/views/components/nav-link.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-@props(['active'])
-
-@php
-    $classes =
-        $active ?? false
-            ? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out'
-            : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out';
-@endphp
-
-<a {{ $attributes->merge(['class' => $classes]) }}>
-    {{ $slot }}
-</a>
--- a/resources/views/components/preview-footer.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-<a href="{{ $route }}" class="text-blue-600 text-sm hover:underline">{{ $label }}</a>
--- a/resources/views/components/preview-list-card.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-@props([
-    'title', // e.g. 'Upcoming Milestones'
-    'icon', // SVG (pass as a Blade include or html)
-    'items', // Collection of items to show
-    'itemView', // Partial for rendering each item
-    'viewData' => [], // Additional data to pass to the partial
-    'empty' => 'Nothing to show.',
-    'footer' => null, // Optional (e.g. a "View All" link)
-    'headerButton' => null, // Optional (e.g. a "Add" link)
-])
-
-<x-card :icon="$icon" :title="$title" :headerButton="$headerButton" :footer="$footer">
-    @if ($items->count())
-        <ul class="divide-y divide-gray-100">
-            @foreach ($items as $item)
-                @include($itemView, array_merge(['item' => $item], $viewData ?? []))
-            @endforeach
-        </ul>
-    @else
-        <div class="text-gray-400 text-sm">{{ $empty }}</div>
-    @endif
-</x-card>
--- a/resources/views/components/primary-button.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-<button
-    {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
-    {{ $slot }}
-</button>
--- a/resources/views/components/responsive-nav-link.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-@props(['active'])
-
-@php
-    $classes =
-        $active ?? false
-            ? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 text-start text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out'
-            : 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out';
-@endphp
-
-<a {{ $attributes->merge(['class' => $classes]) }}>
-    {{ $slot }}
-</a>
--- a/resources/views/components/secondary-button.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-<button
-    {{ $attributes->merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 transition ease-in-out duration-150']) }}>
-    {{ $slot }}
-</button>
--- a/resources/views/components/text-input.blade.php	Mon Jun 09 23:07:17 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-@props(['disabled' => false])
-
-<input @disabled($disabled)
-    {{ $attributes->merge(['class' => 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm']) }}>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Database/Builder.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,23 @@
+<?php
+
+namespace Wizard\Framework\Database;
+
+use Illuminate\Database\Eloquent\Builder as BaseBuilder;
+
+class Builder extends BaseBuilder {
+	
+	public $named_joins = [];
+
+	/*
+	 * Add's a named join to the query, this will only add the joins
+	 * a single time, and keep track of joins that are already in the query.
+	 *
+	 */
+	public function addNamedJoin($join = '') {
+		$available_joins = $this->model?->named_joins() ?? [];
+		if(isset($available_joins[$join]) && ($named_joins[$join] ?? false) == false) {
+			$this->named_joins[$join] = true;
+			$available_joins[$join]($this);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Database/MarkdownParser.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,65 @@
+<?php
+
+namespace Libraries;
+
+class MarkdownParser
+{
+    public static function toHtml(string $markdown): string
+    {
+        $code_block_pattern = '/```([a-z]+)\s+(.*?)\s+```/si';
+        $inline_code_pattern = '/`\s*(.*?)\s*`/si';
+        $matches = [];
+
+        preg_match_all($code_block_pattern, $markdown, $matches, PREG_SET_ORDER);
+        $code_blocks = [];
+        foreach ($matches as $index => $match) {
+            $code_blocks[] = [
+                'language' => $match[1],
+                'code' => $match[2],
+            ];
+        }
+
+        foreach ($matches as $index => $match) {
+            $markdown = preg_replace('/'.preg_quote($match[0], '/').'/', '[CODE_BLOCK_'.$index.']', $markdown, 1);
+        }
+
+        preg_match_all($inline_code_pattern, $markdown, $matches, PREG_SET_ORDER);
+        $inline_code = [];
+        foreach ($matches as $index => $match) {
+            $inline_code[] = [
+                'code' => $match[1],
+            ];
+        }
+
+        foreach ($matches as $index => $match) {
+            $markdown = preg_replace('/'.preg_quote($match[0], '/').'/', '[INLINE_CODE_'.$index.']', $markdown, 1);
+        }
+
+        $markdown = preg_replace('/`(.+?)`/', '<code>$1</code>', $markdown);
+        $markdown = preg_replace('/\#{6}\s(.+)/', '<h6>$1</h6>', $markdown);
+        $markdown = preg_replace('/\#{5}\s(.+)/', '<h5>$1</h5>', $markdown);
+        $markdown = preg_replace('/\#{4}\s(.+)/', '<h4>$1</h4>', $markdown);
+        $markdown = preg_replace('/\#{3}\s(.+)/', '<h3>$1</h3>', $markdown);
+        $markdown = preg_replace('/\#{2}\s(.+)/', '<h2>$1</h2>', $markdown);
+        $markdown = preg_replace('/\#\s(.+)/', '<h1>$1</h1>', $markdown);
+        $markdown = preg_replace('/\*\*\*(.+?)\*\*\*/', '<strong><em>$1</em></strong>', $markdown);
+        $markdown = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $markdown);
+        $markdown = preg_replace('/\*(.+?)\*/', '<em>$1</em>', $markdown);
+        $markdown = preg_replace('/\[(.+?)\]\((.+?)\)/', '<a href="$2">$1</a>', $markdown);
+        $markdown = preg_replace("/\r\n|\r|\n/", '<br>', $markdown);
+
+        foreach ($code_blocks as $index => $code_block) {
+            $language = $code_block['language'];
+            $language = $language == 'blade' ? 'html' : $language;
+            $code_block['code'] = '<pre><code class="match-braces language-'.$language.'">'.htmlentities($code_block['code']).'</code></pre>';
+            $markdown = str_replace('[CODE_BLOCK_'.$index.']', $code_block['code'], $markdown);
+        }
+
+        foreach ($inline_code as $index => $code) {
+            $code['code'] = '<code>'.htmlentities($code['code']).'</code>';
+            $markdown = str_replace('[INLINE_CODE_'.$index.']', $code['code'], $markdown);
+        }
+
+        return $markdown;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Database/MyBlueprint.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,102 @@
+<?php
+
+namespace Libraries;
+
+use Illuminate\Database\Schema\ColumnDefinition;
+
+class MyBlueprint extends \Illuminate\Database\Schema\Blueprint
+{
+    /**
+     * Generates the base columns for a regular table
+     */
+    public function baseColumns(): void
+    {
+        $this->id();
+        $this->reference('users', 'created_by')->nullable();
+        $this->reference('users', 'updated_by')->nullable();
+        $this->timestamps();
+        $this->softDeletes();
+    }
+
+    /**
+     * @param  mixed  $abbreviation
+     */
+    public function baseTextColumns($abbreviation = false): void
+    {
+        $this->string('name', 255);
+        $this->text('description')->nullable();
+        if ($abbreviation) {
+            $this->string('abbreviation', 50)->nullable();
+        }
+    }
+
+    public function pivotColumns(string $table1, string $table2): void
+    {
+        $this->comment('PIVOT:'.json_encode(['table1' => $table1, 'table2' => $table2]));
+        $this->id();
+        $this->reference($table1);
+        $this->reference($table2);
+    }
+
+    public function settingsColumns(): void
+    {
+        $this->baseColumns();
+        $this->baseTextColumns();
+    }
+
+    /**
+     * @param  mixed  $keep_column
+     */
+    public function dropReference(string $table, ?string $override_column_name = null, $keep_column = false): void
+    {
+        // set up relevant strings
+        $column_name = self::get_foreign_column_name($table, $override_column_name);
+        $fk_name = self::get_foreign_key_name($this->getTable(), $table, $column_name);
+
+        // drop the reference
+        $this->dropForeign($fk_name);
+
+        // drop the column if necessary
+        if (! $keep_column) {
+            $this->dropColumn($column_name);
+        }
+    }
+
+    public function reference(string $table, ?string $override_column_name = null): ColumnDefinition
+    {
+        // set up relevant strings
+        $column_name = self::get_foreign_column_name($table, $override_column_name);
+        $fk_name = self::get_foreign_key_name($this->getTable(), $table, $column_name);
+
+        // create the column
+        $column = $this->unsignedBigInteger($column_name);
+
+        // create the foreign key
+        $this->foreign($column_name, $fk_name)->references('id')->on($table);
+
+        return $column;
+    }
+
+    protected static function get_foreign_column_name(string $input, ?string $override = null): string
+    {
+        return ! is_null($override) ? $override : \Str::singular($input).'_id';
+    }
+
+    /*
+     * Generates a foreign key name
+     * 'FK_' + current table name + foreign column name
+     *
+     * */
+    protected static function get_foreign_key_name(string $current_table, string $table, string $column_name): string
+    {
+        $fk_name = 'FK_';
+
+        // add the current table name
+        $fk_name .= \Str::singular($current_table);
+
+        // add the foreign column name
+        $fk_name .= '_'.$column_name;
+
+        return $fk_name;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Database/MySchema.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,32 @@
+<?php
+
+namespace Libraries;
+
+use Illuminate\Database\Schema\Builder;
+use Illuminate\Support\Facades\Schema as BaseSchema;
+
+class MySchema extends BaseSchema
+{
+    /**
+     * Get a schema builder instance for a connection.
+     *
+     * @param  string|null  $name
+     */
+    public static function connection($name): Builder
+    {
+        return self::customizedSchemaBuilder($name);
+    }
+
+    /**
+     * Retrieves an instance of the schema `Builder` with a customized `Blueprint` class.
+     */
+    public static function customizedSchemaBuilder(?string $name = null): Builder
+    {
+        /** @var Builder $builder */
+        $builder = static::$app['db']->connection($name)->getSchemaBuilder();
+
+        $builder->blueprintResolver(static fn ($table, $callback, $prefix) => new MyBlueprint($table, $callback, $prefix));
+
+        return $builder;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Database/SchemaDrawer.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,103 @@
+<?php
+
+namespace Libraries;
+
+use Illuminate\Support\Facades\Schema;
+
+class SchemaDrawer
+{
+    public $schema;
+
+    public $table;
+
+    public $tables;
+
+    public function __construct()
+    {
+        $this->tables = $this->getTables();
+    }
+
+    public static function createSchema($file_name = null)
+    {
+        $sd = new self;
+
+        $schema = $sd->list_tables_and_fks();
+        if (is_null($file_name)) {
+            return $schema;
+        } else {
+            $f = fopen($file_name, 'w+');
+            fwrite($f, $schema);
+            fclose($f);
+        }
+
+        return true;
+    }
+
+    protected static function getTableColumns(string $table_name)
+    {
+        return Schema::getColumns($table_name);
+    }
+
+    protected static function getTableForeignKeys(string $table_name)
+    {
+        return Schema::getForeignKeys($table_name);
+    }
+
+    /**
+     * Get the tables in the schema.
+     */
+    protected function getTables()
+    {
+        if (is_null($this->tables)) {
+            $key = 'DATABASE()';
+            $schema = \DB::select('SELECT DATABASE()')[0]->$key;
+            $this->tables = Schema::getTables(schema: [$schema]);
+        }
+
+        return $this->tables;
+    }
+
+    public function list_tables_and_fks(): string
+    {
+        $fk_labels = false;
+        $str = '
+digraph mydb { 
+	fontname="Helvetica,Arial,sans-serif"
+	graph [layout="circo"]
+	node [fontname="Helvetica,Arial,sans-serif", shape="record"]
+	edge [fontname="Helvetica,Arial,sans-serif"]
+';
+        foreach ($this->tables as $table) {
+            $table_name = $table['name'];
+            $str .= "\t".$table_name.'[label="{'.$table_name;
+            foreach (self::getTableColumns($table_name) as $column) {
+                $col_name = $column['name'];
+                $str .= ' | <'.$col_name.'> '.$col_name;
+                // TODO: should we show the type?
+            }
+            $str .= '}"]'."\n";
+
+            $fks = self::getTableForeignKeys($table_name);
+            foreach ($fks as $fk) {
+                $name = $fk['name'];
+
+                if (! (str_contains($name, 'created_by') || str_contains($name, 'updated_by'))) {
+                    $local_column = $fk['columns'];
+                    $local_column = (isset($local_column[0])) ? ':'.$local_column[0] : '';
+                    $foreign_column = $fk['foreign_columns'];
+                    $foreign_column = (isset($foreign_column[0])) ? ':'.$foreign_column[0] : '';
+                    $str .= "\t".$table_name.$local_column.' -> '.$fk['foreign_table'].$foreign_column;
+                    if ($fk_labels) {
+                        $str .= '[label="'.$name."\"];\n";
+                    } else {
+                        $str .= ";\n";
+                    }
+                }
+            }
+            $str .= "\n";
+        }
+        $str .= '}';
+
+        return $str;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/FrameworkServiceProvider.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,25 @@
+<?php
+
+namespace Wizard\Framework;
+
+use Illuminate\Support\ServiceProvider;
+
+class FrameworkServiceProvider extends ServiceProvider {
+	public function register():void
+ 	{
+		$this->app->bind('db.schema', fn () => \Libraries\MySchema::customizedSchemaBuilder()); 
+	}
+
+	public function boot(): void {
+		$publish_path = __DIR__.'/../publishable';
+		$this->publishes([
+			$publish_path.'/resources/views/components' => resource_path('views/components'),
+			$publish_path.'/vite.config.js' => base_path(),
+		]);
+
+		foreach (glob(base_path('routes/resources/*.php')) as $routeFile) {
+            $this->loadRoutesFrom($routeFile);
+     }
+
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Http/Controllers/BaseController.php	Wed Jun 18 22:28:47 2025 -0400
@@ -0,0 +1,7 @@
+<?php
+
+namespace Wizard\Framework\Http\Controllers;
+
+abstract class BaseController {
+
+}
--- a/src/Models/BaseModel.php	Mon Jun 09 23:07:17 2025 -0400
+++ b/src/Models/BaseModel.php	Wed Jun 18 22:28:47 2025 -0400
@@ -3,12 +3,15 @@
 namespace Wizard\Framework\Models;
 
 use Wizard\Framework\Traits\Filterable;
+use Wizard\Framework\Database\Builder;
 use Illuminate\Database\Eloquent\Model;
 
 class BaseModel extends Model
 {
     use Filterable;
 
+		protected static string $builder = Builder::class;
+
     protected $default_relations = [];
 
     public static function boot(): void
@@ -25,6 +28,7 @@
         });
     }
 
+
     protected static function load_auxilary_data()
     {
         $data = [];
@@ -55,6 +59,10 @@
         return static::load_auxilary_data();
     }
 
+		public static function named_joins() {
+			return [];
+		}
+
     /**
      * Retrieve a query builder instance with default relations loaded.
      *
--- a/src/Traits/Filterable.php	Mon Jun 09 23:07:17 2025 -0400
+++ b/src/Traits/Filterable.php	Wed Jun 18 22:28:47 2025 -0400
@@ -6,19 +6,6 @@
 {
     protected static $filters = [];
 
-    // End goal: 'column_name' 'operator' 'value'
-    // string:   name LIKE '%test%'
-    // value:    id = 3
-    // date:
-    //
-    //
-    // table
-    // column
-    // operator ? based on the value?
-    // value
-    // required joins
-    //
-
     public function scopeFilter($query, $validated)
     {
         $filters = static::$filters;