@kazzle/app 0.1.927 → 0.1.930

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/auth-react.d.ts +20 -0
  2. package/dist/auth-react.js +52 -0
  3. package/dist/auth-schema.d.ts +12 -0
  4. package/dist/auth-schema.js +66 -0
  5. package/dist/auth.d.ts +50 -0
  6. package/dist/auth.js +87 -0
  7. package/dist/connections.d.ts +11 -0
  8. package/dist/connections.js +56 -0
  9. package/dist/index.d.ts +7 -0
  10. package/dist/sdk.d.ts +1 -0
  11. package/dist/sdk.js +13 -0
  12. package/dist/vendor/kazzle/browsers.d.ts +56 -0
  13. package/dist/vendor/kazzle/browsers.js +88 -0
  14. package/dist/vendor/kazzle/browsers.profiles.d.ts +16 -0
  15. package/dist/vendor/kazzle/browsers.profiles.js +26 -0
  16. package/dist/vendor/kazzle/browsers.tabs.d.ts +62 -0
  17. package/dist/vendor/kazzle/browsers.tabs.js +132 -0
  18. package/dist/vendor/kazzle/computers.d.ts +35 -0
  19. package/dist/vendor/kazzle/computers.desktop.d.ts +152 -0
  20. package/dist/vendor/kazzle/computers.desktop.js +144 -0
  21. package/dist/vendor/kazzle/computers.fs.d.ts +44 -0
  22. package/dist/vendor/kazzle/computers.fs.js +37 -0
  23. package/dist/vendor/kazzle/computers.js +55 -0
  24. package/dist/vendor/kazzle/computers.terminals.d.ts +36 -0
  25. package/dist/vendor/kazzle/computers.terminals.js +38 -0
  26. package/dist/vendor/kazzle/generated/operations.d.ts +8 -0
  27. package/dist/vendor/kazzle/generated/operations.js +137 -0
  28. package/dist/vendor/kazzle/http.d.ts +39 -0
  29. package/dist/vendor/kazzle/http.js +103 -0
  30. package/dist/vendor/kazzle/index.d.ts +11 -0
  31. package/dist/vendor/kazzle/index.js +12 -0
  32. package/dist/vendor/kazzle/kazzle.d.ts +10 -0
  33. package/dist/vendor/kazzle/kazzle.js +18 -0
  34. package/dist/vendor/kazzle/sdk.types.d.ts +134 -0
  35. package/dist/vendor/kazzle/sdk.types.js +6 -0
  36. package/dist/vendor/kazzle/sse.d.ts +45 -0
  37. package/dist/vendor/kazzle/sse.js +112 -0
  38. package/package.json +43 -1
  39. package/templates/login-app/KAZZLE.md +18 -0
  40. package/templates/login-app/components/server/index.ts +93 -0
  41. package/templates/login-app/components/server/package.json +25 -0
  42. package/templates/login-app/components/server/tsconfig.json +11 -0
  43. package/templates/login-app/components/ui/index.html +12 -0
  44. package/templates/login-app/components/ui/package.json +29 -0
  45. package/templates/login-app/components/ui/public/favicon.svg +4 -0
  46. package/templates/login-app/components/ui/src/App.tsx +107 -0
  47. package/templates/login-app/components/ui/src/api.ts +37 -0
  48. package/templates/login-app/components/ui/src/main.tsx +13 -0
  49. package/templates/login-app/components/ui/src/styles/theme.css +181 -0
  50. package/templates/login-app/components/ui/src/vite-env.d.ts +1 -0
  51. package/templates/login-app/components/ui/tsconfig.json +21 -0
  52. package/templates/login-app/components/ui/vite.config.ts +19 -0
  53. package/templates/login-app/kazzle.config.ts +13 -0
  54. package/templates/login-app/package.json +17 -0
  55. package/templates/manifest.json +1 -0
  56. package/templates/ui-db-app/components/ui/tsconfig.json +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kazzle/app",
3
- "version": "0.1.927",
3
+ "version": "0.1.930",
4
4
  "description": "Contracts, tool helpers, Vite helper, and templates for building Kazzle apps.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -24,6 +24,22 @@
24
24
  "types": "./dist/client.d.ts",
25
25
  "import": "./dist/client.js"
26
26
  },
27
+ "./connections": {
28
+ "types": "./dist/connections.d.ts",
29
+ "import": "./dist/connections.js"
30
+ },
31
+ "./auth": {
32
+ "types": "./dist/auth.d.ts",
33
+ "import": "./dist/auth.js"
34
+ },
35
+ "./auth/schema": {
36
+ "types": "./dist/auth-schema.d.ts",
37
+ "import": "./dist/auth-schema.js"
38
+ },
39
+ "./auth/react": {
40
+ "types": "./dist/auth-react.d.ts",
41
+ "import": "./dist/auth-react.js"
42
+ },
27
43
  "./vite": {
28
44
  "types": "./dist/vite.d.ts",
29
45
  "import": "./dist/vite.js"
@@ -32,6 +48,10 @@
32
48
  "types": "./dist/pwa.d.ts",
33
49
  "import": "./dist/pwa.js"
34
50
  },
51
+ "./sdk": {
52
+ "types": "./dist/sdk.d.ts",
53
+ "import": "./dist/sdk.js"
54
+ },
35
55
  "./templates": {
36
56
  "types": "./dist/templates.d.ts",
37
57
  "import": "./dist/templates.js"
@@ -61,10 +81,26 @@
61
81
  "workbox-window": "^7.4.1"
62
82
  },
63
83
  "peerDependencies": {
84
+ "kazzle": "^0.1.0",
85
+ "better-auth": ">=1.6.0 <1.7.0",
86
+ "pg": "^8.11.0",
87
+ "react": ">=18",
64
88
  "vite": ">=5",
65
89
  "zod": "^3 || ^4"
66
90
  },
67
91
  "peerDependenciesMeta": {
92
+ "kazzle": {
93
+ "optional": true
94
+ },
95
+ "better-auth": {
96
+ "optional": true
97
+ },
98
+ "pg": {
99
+ "optional": true
100
+ },
101
+ "react": {
102
+ "optional": true
103
+ },
68
104
  "vite": {
69
105
  "optional": true
70
106
  },
@@ -73,7 +109,13 @@
73
109
  }
74
110
  },
75
111
  "devDependencies": {
112
+ "kazzle": "workspace:*",
76
113
  "@types/node": "^25.1.0",
114
+ "@types/pg": "^8.11.0",
115
+ "@types/react": "^19.0.0",
116
+ "better-auth": ">=1.6.0 <1.7.0",
117
+ "pg": "^8.11.0",
118
+ "react": "^19.0.0",
77
119
  "typescript": "7.0.2",
78
120
  "vite": "^6.3.5",
79
121
  "zod": "^4.3.6"
@@ -0,0 +1,18 @@
1
+ # Kazzle template context
2
+
3
+ This app was created from the `login` template. It already includes end-user
4
+ login (Google via Kazzle), a React UI, a Hono process API, and Postgres wiring.
5
+
6
+ Template-owned setup:
7
+ - `kazzle.config.ts` declares the UI and server components and `appLogin: true`. Database env vars are injected by `app.create`.
8
+ - `components/server/index.ts` mounts the login flow (`createKazzleAuth` + `mountKazzleAuth` from `@kazzle/app/auth`) and owns the per-user notes API.
9
+ - `components/ui/src/App.tsx` renders login state via `useAppUser()` / `signInWithKazzle()` / `signOutOfApp()` from `@kazzle/app/auth/react`.
10
+ - The database schema (auth tables + notes) is bootstrapped by template setup before the app is handed back.
11
+
12
+ Login rules — these keep sign-in working:
13
+ - Auth is SDK code. Never write session/cookie/callback logic, never call the Kazzle broker or Google directly, never edit `/api/auth` handling beyond `mountKazzleAuth`.
14
+ - The `auth` Postgres schema (`auth."user"`, `auth."session"`, `auth."account"`, `auth."verification"`) is owned by the SDK. Never ALTER those tables. Per-user data goes in your own public-schema tables with `user_id TEXT REFERENCES auth."user"(id) ON DELETE CASCADE` — see `notes`.
15
+ - Server routes that touch user data start with `await kazzleAuth.getAppUser(c.req.raw)`; null → return 401.
16
+ - New backend routes the browser calls must be added to `proxyPaths` in `components/ui/vite.config.ts`. `/api/auth` must stay in that list.
17
+
18
+ Do not rerun `db.connect`, rewrite manifest env refs, recreate server boilerplate, or reinstall dependencies as setup repair after `app.create` reports template setup complete. Change these files only when the user asks for behavior beyond this template.
@@ -0,0 +1,93 @@
1
+ /**
2
+ * {{APP_NAME}} — server component for the login template.
3
+ *
4
+ * End-user login via Kazzle (Google today) plus a per-user notes API that
5
+ * shows the pattern for user-owned data:
6
+ *
7
+ * - Auth is SDK code: `createKazzleAuth()` + `mountKazzleAuth()` wire the
8
+ * whole login flow. Do NOT write auth logic (sessions, cookies, callbacks)
9
+ * by hand — extend around it.
10
+ * - Users live in this app's own database, in the `auth` Postgres schema
11
+ * (auth."user", auth."session", …). NEVER modify those tables. Per-user
12
+ * app data goes in your own public-schema tables with a
13
+ * `user_id TEXT REFERENCES auth."user"(id)` column — like `notes` below.
14
+ * - Every route that touches user data starts with `getAppUser(request)`;
15
+ * null means logged out → 401. The browser sends the session cookie
16
+ * automatically (same-origin /api/auth + API paths via the UI proxy).
17
+ */
18
+
19
+ import { serve } from '@hono/node-server';
20
+ import { Hono } from 'hono';
21
+ import { cors } from 'hono/cors';
22
+ import postgres from 'postgres';
23
+ import { createKazzleAuth, mountKazzleAuth } from '@kazzle/app/auth';
24
+
25
+ const databaseUrl = process.env.DATABASE_URL;
26
+ if (!databaseUrl) throw new Error('DATABASE_URL is required');
27
+
28
+ const sql = postgres(databaseUrl);
29
+ const kazzleAuth = createKazzleAuth();
30
+ const app = new Hono();
31
+
32
+ app.use('*', cors());
33
+ app.get('/health', (c) => c.json({ ok: true }));
34
+
35
+ // All of /api/auth/* — sign-in, callback, session, sign-out. SDK-owned.
36
+ mountKazzleAuth(app, kazzleAuth);
37
+
38
+ // ── Who am I ─────────────────────────────────────────────────────────────
39
+
40
+ app.get('/me', async (c) => {
41
+ const user = await kazzleAuth.getAppUser(c.req.raw);
42
+ if (!user) return c.json({ error: 'Not signed in' }, 401);
43
+ return c.json(user);
44
+ });
45
+
46
+ // ── Notes (per-user data pattern) ────────────────────────────────────────
47
+
48
+ app.get('/notes', async (c) => {
49
+ const user = await kazzleAuth.getAppUser(c.req.raw);
50
+ if (!user) return c.json({ error: 'Not signed in' }, 401);
51
+ const rows = await sql`
52
+ SELECT id, body, created_at
53
+ FROM notes
54
+ WHERE user_id = ${user.id}
55
+ ORDER BY created_at DESC
56
+ `;
57
+ return c.json(rows);
58
+ });
59
+
60
+ app.post('/notes', async (c) => {
61
+ const user = await kazzleAuth.getAppUser(c.req.raw);
62
+ if (!user) return c.json({ error: 'Not signed in' }, 401);
63
+ const { body } = await c.req.json<{ body?: string }>();
64
+ const text = body?.trim();
65
+ if (!text) return c.json({ error: 'Note text is required' }, 400);
66
+ const [note] = await sql`
67
+ INSERT INTO notes (user_id, body)
68
+ VALUES (${user.id}, ${text})
69
+ RETURNING id, body, created_at
70
+ `;
71
+ return c.json(note, 201);
72
+ });
73
+
74
+ app.delete('/notes/:id', async (c) => {
75
+ const user = await kazzleAuth.getAppUser(c.req.raw);
76
+ if (!user) return c.json({ error: 'Not signed in' }, 401);
77
+ const id = Number(c.req.param('id'));
78
+ if (!Number.isFinite(id)) return c.json({ error: 'Invalid note id' }, 400);
79
+ // WHERE user_id keeps one user from deleting another's note by guessing ids.
80
+ await sql`DELETE FROM notes WHERE id = ${id} AND user_id = ${user.id}`;
81
+ return c.json({ ok: true });
82
+ });
83
+
84
+ // ─────────────────────────────────────────────────────────────────────────
85
+ // DO NOT CHANGE THE NEXT 4 LINES. DO NOT ADD FALLBACKS. DO NOT HARDCODE.
86
+ // PORT and HOST are injected by `kazzle run`. Missing values must throw.
87
+ // ─────────────────────────────────────────────────────────────────────────
88
+ if (!process.env.PORT || !process.env.HOST) {
89
+ throw new Error('PORT and HOST must be set by "kazzle run" — never hardcode them.');
90
+ }
91
+ const port = Number(process.env.PORT);
92
+ const hostname = process.env.HOST;
93
+ serve({ fetch: app.fetch, port, hostname });
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "{{APP_SLUG}}-server",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "bun run index.ts",
8
+ "dev": "kazzle run -- bun --watch index.ts",
9
+ "typecheck": "tsc --noEmit --allowImportingTsExtensions",
10
+ "check": "bun run typecheck"
11
+ },
12
+ "dependencies": {
13
+ "@hono/node-server": "^1.13.0",
14
+ "@kazzle/app": "{{KAZZLE_APP_VERSION}}",
15
+ "better-auth": ">=1.6.0 <1.7.0",
16
+ "hono": "^4.12.16",
17
+ "pg": "^8.11.0",
18
+ "postgres": "^3.4.5"
19
+ },
20
+ "devDependencies": {
21
+ "@types/bun": "^1.3.6",
22
+ "@types/pg": "^8.11.0",
23
+ "typescript": "7.0.2"
24
+ }
25
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "types": ["bun"]
9
+ },
10
+ "include": ["*.ts"]
11
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{APP_NAME}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "{{APP_SLUG}}",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "kazzle run -- vite",
8
+ "typecheck": "tsc --noEmit",
9
+ "build": "vite build",
10
+ "preview": "kazzle run -- vite preview",
11
+ "check": "bun run typecheck && bun run build"
12
+ },
13
+ "dependencies": {
14
+ "@kazzle/app": "{{KAZZLE_APP_VERSION}}",
15
+ "@vitejs/plugin-react": "^4.5.2",
16
+ "better-auth": ">=1.6.0 <1.7.0",
17
+ "react": "^19.1.0",
18
+ "react-dom": "^19.1.0",
19
+ "vite": "^6.3.5"
20
+ },
21
+ "devDependencies": {
22
+ "@tailwindcss/vite": "^4.1.0",
23
+ "@types/node": "^25.1.0",
24
+ "@types/react": "^19.1.0",
25
+ "@types/react-dom": "^19.1.0",
26
+ "tailwindcss": "^4.1.0",
27
+ "typescript": "7.0.2"
28
+ }
29
+ }
@@ -0,0 +1,4 @@
1
+ <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="64" height="64" rx="18" fill="#6366F1"/>
3
+ <path d="M19 33.5L28 42L46 22" stroke="white" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
4
+ </svg>
@@ -0,0 +1,107 @@
1
+ /**
2
+ * {{APP_NAME}} — login template UI.
3
+ *
4
+ * Two states, driven entirely by `useAppUser()` from @kazzle/app/auth/react:
5
+ * logged out → a login card with one "Continue with Google" button
6
+ * logged in → the app (per-user notes), user chip, sign out
7
+ *
8
+ * All auth is SDK code: `signInWithKazzle()` starts the flow,
9
+ * `signOutOfApp()` ends the session, `useAppUser()` is reactive. There is no
10
+ * auth logic to write in this file — build the app around the user object.
11
+ */
12
+
13
+ import { useEffect, useState, type FormEvent } from 'react';
14
+ import { signInWithKazzle, signOutOfApp, useAppUser, useAppUserPending } from '@kazzle/app/auth/react';
15
+ import { createNote, deleteNote, getNotes, type Note } from './api';
16
+
17
+ export function App() {
18
+ const user = useAppUser();
19
+ const pending = useAppUserPending();
20
+
21
+ if (pending) return <div className="page" />;
22
+ if (!user) return <LoginScreen />;
23
+ return <NotesScreen name={user.name ?? user.email} image={user.image} />;
24
+ }
25
+
26
+ function LoginScreen() {
27
+ return (
28
+ <div className="page items-center justify-center">
29
+ <div className="card stack max-w-sm w-full items-center text-center">
30
+ <h1 className="text-2xl font-semibold">{'{{APP_NAME}}'}</h1>
31
+ <p className="text-muted">Sign in to keep your notes in one place.</p>
32
+ <button className="btn w-full" onClick={() => void signInWithKazzle('google')}>
33
+ Continue with Google
34
+ </button>
35
+ </div>
36
+ </div>
37
+ );
38
+ }
39
+
40
+ function NotesScreen({ name, image }: { name: string; image: string | null }) {
41
+ const [notes, setNotes] = useState<Note[]>([]);
42
+ const [draft, setDraft] = useState('');
43
+ const [error, setError] = useState<string | null>(null);
44
+
45
+ useEffect(() => {
46
+ getNotes().then(setNotes).catch((err: Error) => setError(err.message));
47
+ }, []);
48
+
49
+ async function addNote(event: FormEvent) {
50
+ event.preventDefault();
51
+ const body = draft.trim();
52
+ if (!body) return;
53
+ try {
54
+ const note = await createNote(body);
55
+ setNotes((current) => [note, ...current]);
56
+ setDraft('');
57
+ } catch (err) {
58
+ setError((err as Error).message);
59
+ }
60
+ }
61
+
62
+ async function removeNote(id: number) {
63
+ try {
64
+ await deleteNote(id);
65
+ setNotes((current) => current.filter((note) => note.id !== id));
66
+ } catch (err) {
67
+ setError((err as Error).message);
68
+ }
69
+ }
70
+
71
+ return (
72
+ <div className="page">
73
+ <header className="row justify-between">
74
+ <h1 className="text-xl font-semibold">{'{{APP_NAME}}'}</h1>
75
+ <div className="row">
76
+ {image && <img src={image} alt="" className="size-8 rounded-full" referrerPolicy="no-referrer" />}
77
+ <span className="text-muted">{name}</span>
78
+ <button className="btn" onClick={() => void signOutOfApp().then(() => window.location.reload())}>
79
+ Sign out
80
+ </button>
81
+ </div>
82
+ </header>
83
+
84
+ <form className="row" onSubmit={addNote}>
85
+ <input
86
+ className="input flex-1"
87
+ placeholder="Write a note…"
88
+ value={draft}
89
+ onChange={(event) => setDraft(event.target.value)}
90
+ />
91
+ <button className="btn" type="submit">Add</button>
92
+ </form>
93
+
94
+ {error && <p className="text-red-400">{error}</p>}
95
+
96
+ <div className="stack">
97
+ {notes.map((note) => (
98
+ <div key={note.id} className="card row justify-between">
99
+ <span>{note.body}</span>
100
+ <button className="btn" onClick={() => void removeNote(note.id)}>Delete</button>
101
+ </div>
102
+ ))}
103
+ {notes.length === 0 && !error && <p className="text-muted">No notes yet — your notes are only visible to you.</p>}
104
+ </div>
105
+ </div>
106
+ );
107
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Thin wrappers over the server's REST API. The browser sends the session
3
+ * cookie automatically — no auth headers to add. A 401 means logged out; the
4
+ * App component treats that as "show the login screen".
5
+ */
6
+
7
+ export interface Note {
8
+ id: number;
9
+ body: string;
10
+ created_at: string;
11
+ }
12
+
13
+ async function request<T>(path: string, init?: RequestInit): Promise<T> {
14
+ const res = await fetch(path, {
15
+ ...init,
16
+ headers: {
17
+ 'Content-Type': 'application/json',
18
+ ...init?.headers,
19
+ },
20
+ });
21
+ if (!res.ok) {
22
+ throw new Error(`Request failed: ${res.status} ${await res.text()}`);
23
+ }
24
+ return res.json() as Promise<T>;
25
+ }
26
+
27
+ export function getNotes(): Promise<Note[]> {
28
+ return request<Note[]>('/notes');
29
+ }
30
+
31
+ export function createNote(body: string): Promise<Note> {
32
+ return request<Note>('/notes', { method: 'POST', body: JSON.stringify({ body }) });
33
+ }
34
+
35
+ export function deleteNote(id: number): Promise<{ ok: boolean }> {
36
+ return request<{ ok: boolean }>(`/notes/${id}`, { method: 'DELETE' });
37
+ }
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { registerAppSW } from '@kazzle/app/pwa';
4
+ import { App } from './App';
5
+ import './styles/theme.css';
6
+
7
+ registerAppSW();
8
+
9
+ createRoot(document.getElementById('root')!).render(
10
+ <React.StrictMode>
11
+ <App />
12
+ </React.StrictMode>,
13
+ );
@@ -0,0 +1,181 @@
1
+ /* ─────────────────────────────────────────────────────────────────────────
2
+ * Kazzle app theme — the ONE styling entry point for this app.
3
+ *
4
+ * This file is the design system. It is imported once (in `main.tsx`) and
5
+ * gives you three things, in order:
6
+ *
7
+ * 1. Tailwind v4 — every utility class (`flex`, `gap-4`,
8
+ * `rounded-xl`, `text-zinc-500`, …) works in
9
+ * your JSX. No config file, no PostCSS setup.
10
+ * 2. Design tokens (@theme) — colors, radius, fonts as CSS variables. Edit
11
+ * these to re-skin the whole app at once.
12
+ * 3. Primitives (@layer) — a few ready-made classes (`.btn`, `.card`,
13
+ * `.input`, `.field`, `.page`, `.stack`,
14
+ * `.row`) so common UI looks good with zero
15
+ * effort.
16
+ *
17
+ * HOW TO STYLE — pick whichever is easier, mix freely:
18
+ * • Tailwind utilities in `className` for one-off layout/spacing.
19
+ * • A primitive class (`.btn`, `.card`) for the common building blocks.
20
+ *
21
+ * WHY IT'S BUILT THIS WAY: styles live in the markup (utilities) or in named
22
+ * primitives here — never in a separate per-screen stylesheet keyed to class
23
+ * names you have to keep in sync. Rewriting a component can't silently strip
24
+ * its styling, because the styling travels with the elements.
25
+ *
26
+ * TO EXTEND: add new design tokens under `@theme`, and add new reusable
27
+ * building blocks under `@layer components`. Keep app-specific one-offs as
28
+ * inline Tailwind utilities in the component instead of adding a class here.
29
+ * ───────────────────────────────────────────────────────────────────────── */
30
+
31
+ @import "tailwindcss";
32
+
33
+ /* ── Design tokens ──────────────────────────────────────────────────────────
34
+ * Exposed to Tailwind as `bg-surface`, `text-fg`, `text-muted`,
35
+ * `bg-accent`, `rounded-app`, etc. Change a value here and every utility and
36
+ * primitive that references it updates at once. */
37
+ @theme {
38
+ --color-bg: #0b0f1a;
39
+ --color-surface: #131a2a;
40
+ --color-surface-raised: #1b2438;
41
+ --color-fg: #e8edf7;
42
+ --color-muted: #94a3b8;
43
+ --color-border: rgba(148, 163, 184, 0.18);
44
+ --color-accent: #6366f1;
45
+ --color-accent-hover: #4f46e5;
46
+ --color-accent-fg: #ffffff;
47
+ --color-danger: #f87171;
48
+
49
+ --radius-app: 0.875rem;
50
+
51
+ --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system,
52
+ BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
53
+ }
54
+
55
+ /* ── Base ────────────────────────────────────────────────────────────────────
56
+ * Sensible document defaults so even unstyled markup reads well. */
57
+ @layer base {
58
+ html,
59
+ body,
60
+ #root {
61
+ min-height: 100%;
62
+ }
63
+
64
+ body {
65
+ margin: 0;
66
+ background: var(--color-bg);
67
+ color: var(--color-fg);
68
+ font-family: var(--font-sans);
69
+ -webkit-font-smoothing: antialiased;
70
+ }
71
+
72
+ h1 {
73
+ font-size: 1.875rem;
74
+ font-weight: 700;
75
+ letter-spacing: -0.02em;
76
+ margin: 0;
77
+ }
78
+
79
+ h2 {
80
+ font-size: 1.25rem;
81
+ font-weight: 600;
82
+ margin: 0;
83
+ }
84
+ }
85
+
86
+ /* ── Primitives ──────────────────────────────────────────────────────────────
87
+ * Reusable building blocks. Use them directly (`<button class="btn btn-primary">`)
88
+ * or layer Tailwind utilities on top. Add new ones here when a pattern repeats. */
89
+ @layer components {
90
+ /* Page wrapper — centers content with comfortable gutters. */
91
+ .page {
92
+ width: min(100% - 2rem, 48rem);
93
+ margin-inline: auto;
94
+ padding-block: 3rem;
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: 1.5rem;
98
+ }
99
+
100
+ /* Vertical / horizontal stacks. */
101
+ .stack {
102
+ display: flex;
103
+ flex-direction: column;
104
+ gap: 0.75rem;
105
+ }
106
+ .row {
107
+ display: flex;
108
+ align-items: center;
109
+ gap: 0.75rem;
110
+ }
111
+
112
+ /* Card surface. */
113
+ .card {
114
+ background: var(--color-surface);
115
+ border: 1px solid var(--color-border);
116
+ border-radius: var(--radius-app);
117
+ padding: 1.25rem;
118
+ }
119
+
120
+ /* Buttons. `.btn` is the neutral base; add `.btn-primary` for the accent. */
121
+ .btn {
122
+ display: inline-flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ gap: 0.5rem;
126
+ padding: 0.625rem 1rem;
127
+ border-radius: var(--radius-app);
128
+ border: 1px solid var(--color-border);
129
+ background: var(--color-surface-raised);
130
+ color: var(--color-fg);
131
+ font: inherit;
132
+ font-weight: 500;
133
+ cursor: pointer;
134
+ transition: background 0.15s, border-color 0.15s, opacity 0.15s;
135
+ }
136
+ .btn:hover {
137
+ background: color-mix(in srgb, var(--color-surface-raised) 80%, white);
138
+ }
139
+ .btn:disabled {
140
+ opacity: 0.5;
141
+ cursor: not-allowed;
142
+ }
143
+ .btn-primary {
144
+ background: var(--color-accent);
145
+ border-color: transparent;
146
+ color: var(--color-accent-fg);
147
+ }
148
+ .btn-primary:hover {
149
+ background: var(--color-accent-hover);
150
+ }
151
+
152
+ /* Text input / select. */
153
+ .input {
154
+ width: 100%;
155
+ min-width: 0;
156
+ padding: 0.625rem 0.875rem;
157
+ border-radius: var(--radius-app);
158
+ border: 1px solid var(--color-border);
159
+ background: var(--color-surface-raised);
160
+ color: var(--color-fg);
161
+ font: inherit;
162
+ outline: none;
163
+ }
164
+ .input:focus {
165
+ border-color: var(--color-accent);
166
+ }
167
+
168
+ /* Labelled field: <label class="field"><span>Label</span><input class="input"/></label> */
169
+ .field {
170
+ display: flex;
171
+ flex-direction: column;
172
+ gap: 0.375rem;
173
+ font-size: 0.875rem;
174
+ color: var(--color-muted);
175
+ }
176
+
177
+ /* Muted helper / secondary text. */
178
+ .muted {
179
+ color: var(--color-muted);
180
+ }
181
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "allowJs": false,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "strict": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "module": "ESNext",
13
+ "moduleResolution": "Bundler",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true,
17
+ "jsx": "react-jsx"
18
+ },
19
+ "include": ["src"],
20
+ "references": []
21
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Vite config for the UI component.
3
+ *
4
+ * Kazzle owns HOST/PORT (`kazzleAppVite`) and production PWA (`kazzleAppPwa`)
5
+ * in `@kazzle/app/vite`. This template declares a backend `server` component;
6
+ * we pass its name plus the relative routes the browser calls. `/api/auth` is
7
+ * the login flow — it MUST stay proxied so auth cookies are same-origin. Add
8
+ * new backend routes to `proxyPaths`.
9
+ */
10
+
11
+ import { defineConfig } from 'vite';
12
+ import react from '@vitejs/plugin-react';
13
+ import tailwindcss from '@tailwindcss/vite';
14
+ import { kazzleAppVite, kazzleAppPwa } from '@kazzle/app/vite';
15
+
16
+ export default defineConfig(({ command }) => ({
17
+ plugins: [react(), tailwindcss(), ...kazzleAppPwa()],
18
+ ...kazzleAppVite(command, { apiComponent: 'server', proxyPaths: ['/api/auth', '/me', '/notes'] }),
19
+ }));