Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude/skills/inertia-table/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export default function Index({ servers }: { servers: InertiaTableData }) {
tableData={servers}
actions={(row) => (
<>
<Link href={route('servers.show', row.id as number)}><Pencil className="size-4" /></Link>
<Link href={`/servers/${row.id}`}><Pencil className="size-4" /></Link>
<button onClick={() => destroy(row.id)}><Trash2 className="size-4" /></button>
</>
)}
Expand Down Expand Up @@ -213,7 +213,7 @@ Global hooks (`Table::globalBeforeQuery`, `Table::globalAfterData`) already exis

**Settings → frontend hooks:** `->withSettings(['polling' => 5])` ships arbitrary data; on the React side register a hook with `registerTableHook('polling', ({ value, refresh }) => { const id = setInterval(refresh, value*1000); return () => clearInterval(id); })`.

**Row click to detail page:** `<InertiaTable onRowClick={(row) => router.visit(route('servers.show', row.id as number))} />`. The component already skips clicks on `a, button, input, select, textarea, [role="button"]`.
**Row click to detail page:** ``<InertiaTable onRowClick={(row) => router.visit(`/servers/${row.id}`)} />``. The component already skips clicks on `a, button, input, select, textarea, [role="button"]`.

**External search input** (e.g. in a page header instead of the toolbar): pass `searchRef={inputRef}` — the built-in search bar hides and the table watches that input.

Expand All @@ -222,7 +222,7 @@ Global hooks (`Table::globalBeforeQuery`, `Table::globalAfterData`) already exis
1. **Every row needs an `id`.** Add `Column::data('id')` if `id` isn't otherwise included.
2. **Dot-notation columns get a `_`-prefixed name** in the row payload (e.g. `provider.name` → row key `_provider_name`). Use the column's display value; don't reach into the row by the dotted path from the frontend.
3. **Sort key vs display name:** for computed columns, set `->accessor('real_db_column')` or sort will target the wrong field.
4. **Ziggy** is a peer dep — Vito4 uses it, so leave `use_ziggy => true`. If you ever turn it off, link columns send pre-resolved URLs.
4. **Link URLs** are resolved server-side. Keep `use_ziggy => false` in `config/inertia-table.php`; frontend link cells read the pre-resolved URL from `href_key`.
5. **TypeScript prop type** for an Inertia page is `InertiaTableData`, not the row type. Cast `row.id` (it's `string | number`).
6. **`ActionsColumn`** does nothing on its own — you must pass `actions={(row) => ...}` to `<InertiaTable />`.
7. **`registerCellComponent` / `registerIcon` must run before render.** Put them in `app.tsx` (or a module imported there), not inside the page component.
Expand Down
5 changes: 3 additions & 2 deletions .github/instructions/frontend.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ description: "React, Inertia, Tailwind v4, and TypeScript frontend standards"

## Inertia & React

- Use root-relative URL paths (`/settings/profile`, `/servers/${server.id}`) for frontend navigation and requests. Encode string path segments and pass query parameters via Axios `params` or `URLSearchParams`.
- Inertia pages live in `resources/js/pages/`. Use `Inertia::render()` from Laravel controllers.
- React components in `resources/js/components/`. Use functional components and hooks.
- Use the `useForm` helper for forms — follow existing patterns in the codebase.
Expand Down Expand Up @@ -37,7 +38,7 @@ dialog.confirm.open({
variant: 'destructive',
confirmLabel: 'Delete',
method: 'delete',
url: route('firewall.destroy', { server: rule.server_id, firewallRule: rule }),
url: `/servers/${rule.server_id}/firewall/${rule.id}`,
});
```

Expand Down Expand Up @@ -67,7 +68,7 @@ export default function FirewallRuleForm({
const form = useForm({ /* seed from props */ });
const submit = (e: FormEvent) => {
e.preventDefault();
form.post(route('firewall.store', { server: serverId }), { onSuccess: () => onOpenChange(false) });
form.post(`/servers/${serverId}/firewall`, { onSuccess: () => onOpenChange(false) });
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
Expand Down
1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,4 @@ sail
resources/views/ssh/
resources/views/wireguard/
resources/views/scribe/
resources/js/ziggy.js
resources/views/mail/*
9 changes: 9 additions & 0 deletions app/Actions/Bootstrap/GetBootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,18 @@ private function configs(): array
'installed' => GithubApp::query()->exists(),
],
'tooling' => $this->tooling(),
'dashboard_urls' => [
'horizon' => $this->dashboardUrl(config('horizon.domain'), config('horizon.path')),
'logs' => $this->dashboardUrl(config('log-viewer.route_domain'), config('log-viewer.route_path')),
],
];
}

private function dashboardUrl(?string $domain, string $path): string
{
return ($domain ? '//'.$domain : '').'/'.trim($path, '/');
}

/**
* @return array<int, array{id: string, label: string, description: string, supported_versions: array<int, string>, commands: array<int, string>}>
*/
Expand Down
74 changes: 0 additions & 74 deletions app/Actions/Ziggy/GetZiggyRoutes.php

This file was deleted.

31 changes: 0 additions & 31 deletions app/Http/Controllers/ZiggyRoutesController.php

This file was deleted.

9 changes: 0 additions & 9 deletions app/Http/Middleware/HandleInertiaRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use Illuminate\Foundation\Inspiring;
use Illuminate\Http\Request;
use Inertia\Middleware;
use Tighten\Ziggy\Ziggy;

class HandleInertiaRequests extends Middleware
{
Expand Down Expand Up @@ -49,8 +48,6 @@ public function share(Request $request): array
{
[$message, $author] = str(Inspiring::quotes()->random())->explode('-');

$ssrEnabled = (bool) config('inertia.ssr.enabled');

/** @var ?User $user */
$user = $request->user();
$currentProject = $user?->currentProject;
Expand Down Expand Up @@ -111,12 +108,6 @@ public function share(Request $request): array
] : null,
'csrf_token' => csrf_token(),
'bootstrap_version' => app(GetBootstrap::class)->version(),
...($ssrEnabled ? [
'ziggy' => fn (): array => [
...(new Ziggy)->toArray(),
'location' => $request->url(),
],
] : []),
'flash' => [
'success' => fn () => $request->session()->get('success'),
'error' => fn () => $request->session()->get('error'),
Expand Down
3 changes: 1 addition & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@
"react/event-loop": "^1.5",
"react/socket": "^1.17",
"spatie/laravel-route-attributes": "^1.24",
"symfony/yaml": "^8.0",
"tightenco/ziggy": "^2.5"
"symfony/yaml": "^8.0"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",
Expand Down
72 changes: 1 addition & 71 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions config/inertia-table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

return [
'use_ziggy' => false,
];
7 changes: 4 additions & 3 deletions docs/4.x/prologue/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ releases.

### Inertia context shrinking

Inertia responses previously shipped ~120 KB+ of mostly-static shared data on every request (the
Ziggy route table, the provider/service catalogue, and more). A new cached bootstrap endpoint now
serves this once and hydrates it on the frontend, cutting typical responses down to a few KB.
Inertia responses previously shipped ~120 KB+ of mostly-static shared data on every request. The
frontend now uses URL paths directly, without a client-side route catalogue or route-script cache.
A cached bootstrap endpoint serves the provider/service catalogue once and hydrates it on the
frontend, cutting typical responses down to a few KB.

## Realtime websocket Updates

Expand Down
11 changes: 4 additions & 7 deletions resources/js/components/app-command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default function AppCommand() {
const query = useQuery<SearchResult[]>({
queryKey: ['search'],
queryFn: async () => {
const response = await axios.get(route('search', { query: queryText }));
const response = await axios.get('/search', { params: { query: queryText } });
return response.data.data;
},
retry: false,
Expand Down Expand Up @@ -100,16 +100,13 @@ export default function AppCommand() {
value={`result-${index}`}
onSelect={() => {
if (result.type === 'server') {
router.post(route('servers.switch', { server: result.id }));
router.post(`/servers/${result.id}/switch`);
} else if (result.type === 'project') {
router.patch(
route('projects.switch', {
project: result.id,
currentPath: window.location.pathname,
}),
`/settings/projects/switch/${result.id}?${new URLSearchParams({ currentPath: window.location.pathname })}`,
);
} else if (result.type === 'site') {
router.post(route('sites.switch', { server: result.parent_id, site: result.id }));
router.post(`/servers/${result.parent_id}/sites/${result.id}/switch`);
}
setOpen(false);
}}
Expand Down
Loading
Loading