@ultimat3/admin 1.1.0 → 1.2.0

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 (2) hide show
  1. package/package.json +14 -14
  2. package/src/dev/server.ts +41 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/admin",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Two dashboards: the /_x framework dev panels and the generated, AI-first app admin",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,18 +31,18 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@ultimat3/action": "1.1.0",
35
- "@ultimat3/ai": "1.1.0",
36
- "@ultimat3/cache": "1.1.0",
37
- "@ultimat3/core": "1.1.0",
38
- "@ultimat3/entity": "1.1.0",
39
- "@ultimat3/i18n": "1.1.0",
40
- "@ultimat3/jobs": "1.1.0",
41
- "@ultimat3/mcp": "1.1.0",
42
- "@ultimat3/money": "1.1.0",
43
- "@ultimat3/policy": "1.1.0",
44
- "@ultimat3/query": "1.1.0",
45
- "@ultimat3/render": "1.1.0",
46
- "@ultimat3/ui": "1.1.0"
34
+ "@ultimat3/action": "1.2.0",
35
+ "@ultimat3/ai": "1.2.0",
36
+ "@ultimat3/cache": "1.2.0",
37
+ "@ultimat3/core": "1.2.0",
38
+ "@ultimat3/entity": "1.2.0",
39
+ "@ultimat3/i18n": "1.2.0",
40
+ "@ultimat3/jobs": "1.2.0",
41
+ "@ultimat3/mcp": "1.2.0",
42
+ "@ultimat3/money": "1.2.0",
43
+ "@ultimat3/policy": "1.2.0",
44
+ "@ultimat3/query": "1.2.0",
45
+ "@ultimat3/render": "1.2.0",
46
+ "@ultimat3/ui": "1.2.0"
47
47
  }
48
48
  }
package/src/dev/server.ts CHANGED
@@ -2,6 +2,9 @@
2
2
  // SQL, policy traces, and caught mail. The refusal is a throw at construction, not a 404 at
3
3
  // request time: an app that boots with /_x mounted in prod has already lost.
4
4
 
5
+ // Type-only, so it is erased and the 46-component barrel stays out of the mount graph — the
6
+ // values arrive through the dynamic `import()` in `shellStyle()`, same reason as `data.ts`.
7
+ import type { ColorRole } from '@ultimat3/ui';
5
8
  import { DevDashboardInProdError } from '../errors';
6
9
  import { defaultDevSources } from './data';
7
10
  import type { DevSources } from './facts';
@@ -78,26 +81,17 @@ const jsonResponse = (body: unknown, status = 200): Response =>
78
81
  const escapeHtml = (value: string): string =>
79
82
  value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
80
83
 
81
- /** Tokens are defined inline: /_x is a standalone page and still owes both themes. */
82
- const SHELL_STYLE = `
83
- :root {
84
- --x-color-bg: 253 246 240; --x-color-surface: 255 255 255; --x-color-fg: 38 34 31;
85
- --x-color-fg-muted: 110 102 94; --x-color-line: 224 216 208; --x-color-accent: 34 122 197;
86
- }
87
- @media (prefers-color-scheme: dark) {
88
- :root {
89
- --x-color-bg: 18 18 20; --x-color-surface: 34 34 39; --x-color-fg: 228 226 222;
90
- --x-color-fg-muted: 150 146 140; --x-color-line: 54 54 60; --x-color-accent: 96 170 240;
91
- }
92
- }
93
- html[data-theme="light"] {
94
- --x-color-bg: 253 246 240; --x-color-surface: 255 255 255; --x-color-fg: 38 34 31;
95
- --x-color-fg-muted: 110 102 94; --x-color-line: 224 216 208; --x-color-accent: 34 122 197;
96
- }
97
- html[data-theme="dark"] {
98
- --x-color-bg: 18 18 20; --x-color-surface: 34 34 39; --x-color-fg: 228 226 222;
99
- --x-color-fg-muted: 150 146 140; --x-color-line: 54 54 60; --x-color-accent: 96 170 240;
100
- }
84
+ /** The six roles /_x paints with. `--x-*` is the admin's namespace; the values are ui's. */
85
+ const SHELL_ROLES = [
86
+ 'bg',
87
+ 'surface-raised',
88
+ 'fg',
89
+ 'fg-muted',
90
+ 'line',
91
+ 'accent',
92
+ ] as const satisfies readonly ColorRole[];
93
+
94
+ const SHELL_LAYOUT = `
101
95
  body { margin: 0; background: rgb(var(--x-color-bg)); color: rgb(var(--x-color-fg));
102
96
  font: 14px/1.5 ui-monospace, monospace; }
103
97
  header { display: flex; gap: 1rem; padding: .75rem 1rem;
@@ -106,12 +100,36 @@ a { color: rgb(var(--x-color-accent)); }
106
100
  main { padding: 1rem; }
107
101
  h1 { font-size: 1rem; margin: 0 1rem 0 0; }
108
102
  p.question { color: rgb(var(--x-color-fg-muted)); margin: 0 0 1rem; }
109
- pre { background: rgb(var(--x-color-surface)); border: 1px solid rgb(var(--x-color-line));
103
+ pre { background: rgb(var(--x-color-surface-raised)); border: 1px solid rgb(var(--x-color-line));
110
104
  padding: 1rem; overflow: auto; }
111
105
  :focus-visible { outline: 2px solid rgb(var(--x-color-accent)); outline-offset: 2px; }
112
106
  `;
113
107
 
108
+ let stylePromise: Promise<string> | undefined;
109
+
110
+ /**
111
+ * Tokens are inlined because /_x is a standalone page with no stylesheet pipeline — but the
112
+ * VALUES are read from `@ultimat3/ui` rather than copied, because the copy that used to live
113
+ * here went stale through a WCAG retune and shipped `line` on `surface-raised` at 1.16:1.
114
+ * Reached by dynamic `import()` for the same reason `data.ts` is: /_x stays out of the
115
+ * production graph, and the 46-component barrel loads only once a panel is actually drawn.
116
+ */
117
+ async function shellStyle(): Promise<string> {
118
+ stylePromise ??= import('@ultimat3/ui').then(({ colorTokens }) => {
119
+ const block = (theme: 'light' | 'dark'): string =>
120
+ SHELL_ROLES.map((role) => `--x-color-${role}: ${colorTokens[theme][role]};`).join(' ');
121
+ return `
122
+ :root { ${block('light')} }
123
+ @media (prefers-color-scheme: dark) { :root { ${block('dark')} } }
124
+ html[data-theme="light"] { ${block('light')} }
125
+ html[data-theme="dark"] { ${block('dark')} }
126
+ ${SHELL_LAYOUT}`;
127
+ });
128
+ return stylePromise;
129
+ }
130
+
114
131
  function shell(
132
+ style: string,
115
133
  basePath: string,
116
134
  panels: readonly DevPanel[],
117
135
  active: DevPanel,
@@ -128,7 +146,7 @@ function shell(
128
146
  <html lang="en"><head><meta charset="utf-8">
129
147
  <meta name="viewport" content="width=device-width, initial-scale=1">
130
148
  <meta name="robots" content="noindex, nofollow">
131
- <title>_x · ${escapeHtml(active.key)}</title><style>${SHELL_STYLE}</style></head>
149
+ <title>_x · ${escapeHtml(active.key)}</title><style>${style}</style></head>
132
150
  <body><header><h1>_x</h1><nav>${tabs}</nav>
133
151
  <a href="${basePath}/${active.key}?json=1">--json</a></header>
134
152
  <main><p class="question">${escapeHtml(active.question)}</p>
@@ -180,7 +198,7 @@ export function devDashboard(opts: DevDashboardOptions = {}): DevDashboard {
180
198
 
181
199
  return wantsJson
182
200
  ? jsonResponse(payload, payload.ok ? 200 : 500)
183
- : new Response(shell(basePath, panels, panel, payload), {
201
+ : new Response(shell(await shellStyle(), basePath, panels, panel, payload), {
184
202
  status: 200,
185
203
  headers: { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' },
186
204
  });