@askrjs/cli 0.0.3 → 0.0.4
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.
- package/README.md +37 -0
- package/dist/add.d.ts +16 -0
- package/dist/add.js +33 -29
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +0 -2
- package/dist/create.d.ts +5 -0
- package/dist/create.js +104 -26
- package/dist/directory-swap-DWoHtx7C.js +35 -0
- package/dist/discovery-Difb7Y4G.js +0 -2
- package/dist/generate.d.ts +5 -0
- package/dist/generate.js +77 -12
- package/dist/is-direct-execution-Cdlr-ZUl.js +0 -2
- package/dist/openapi.d.ts +25 -0
- package/dist/openapi.js +44 -12
- package/dist/planner-VAj7qlxr.js +0 -2
- package/dist/range-YUs9eimn.js +0 -2
- package/dist/registry-CyrRHRG5.js +0 -2
- package/dist/skills/askr-ssr-ssg/SKILL.md +3 -1
- package/dist/{skills-Bh0mEhIx.js → skills-B7CbWur9.js} +89 -26
- package/dist/skills.d.ts +18 -0
- package/dist/skills.js +1 -1
- package/dist/specification-DXnDOC-0.js +0 -2
- package/dist/ssg-config.d.ts +41 -0
- package/dist/ssg-config.js +1 -0
- package/dist/ssg.d.ts +61 -0
- package/dist/ssg.js +360 -24
- package/dist/templates/full-stack/package.json +1 -1
- package/dist/templates/full-stack/src/server/action-registry.ts +0 -1
- package/dist/templates/full-stack/tsconfig.json +1 -1
- package/dist/templates/spa/package.json +2 -3
- package/dist/templates/spa/src/pages/app/_routes.tsx +2 -0
- package/dist/templates/spa/src/pages/public/_routes.tsx +2 -0
- package/dist/templates/spa/tsconfig.json +2 -1
- package/dist/templates/spa/vitest.config.ts +0 -4
- package/dist/templates/ssg/README.md +1 -1
- package/dist/templates/ssg/package.json +1 -1
- package/dist/templates/ssg/ssg.config.ts +7 -0
- package/dist/templates/ssg/tests/ssg-config.test.ts +2 -0
- package/dist/templates/ssg/tsconfig.json +2 -1
- package/dist/templates/ssg/vitest.config.ts +0 -4
- package/dist/templates/ssr/package.json +2 -2
- package/dist/templates/startkit/package.json +2 -2
- package/dist/templates/startkit/src/lib/mock-data.ts +17 -39
- package/dist/templates/startkit/tsconfig.json +2 -1
- package/dist/update.d.ts +41 -0
- package/dist/update.js +0 -2
- package/dist/writer-D8qe_7ud.js +0 -2
- package/package.json +13 -8
- package/dist/add.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/create.js.map +0 -1
- package/dist/discovery-Difb7Y4G.js.map +0 -1
- package/dist/generate.js.map +0 -1
- package/dist/is-direct-execution-Cdlr-ZUl.js.map +0 -1
- package/dist/openapi.js.map +0 -1
- package/dist/planner-VAj7qlxr.js.map +0 -1
- package/dist/range-YUs9eimn.js.map +0 -1
- package/dist/registry-CyrRHRG5.js.map +0 -1
- package/dist/skills-Bh0mEhIx.js.map +0 -1
- package/dist/specification-DXnDOC-0.js.map +0 -1
- package/dist/ssg.js.map +0 -1
- package/dist/templates/full-stack/src/vite-server.d.ts +0 -5
- package/dist/templates/spa/src/styles.d.ts +0 -1
- package/dist/templates/ssg/src/vite-env.d.ts +0 -5
- package/dist/templates/ssr/src/vite-server.d.ts +0 -7
- package/dist/templates/startkit/src/vite-env.d.ts +0 -5
- package/dist/update.js.map +0 -1
- package/dist/writer-D8qe_7ud.js.map +0 -1
- package/skills/askr-accessibility/SKILL.md +0 -68
- package/skills/askr-accessibility/agents/openai.yaml +0 -4
- package/skills/askr-agent-execution/SKILL.md +0 -85
- package/skills/askr-agent-execution/agents/openai.yaml +0 -4
- package/skills/askr-agent-workflows/SKILL.md +0 -57
- package/skills/askr-agent-workflows/agents/openai.yaml +0 -4
- package/skills/askr-api-integration/SKILL.md +0 -79
- package/skills/askr-api-integration/agents/openai.yaml +0 -4
- package/skills/askr-app-builder/SKILL.md +0 -99
- package/skills/askr-app-builder/agents/openai.yaml +0 -4
- package/skills/askr-auth-access/SKILL.md +0 -74
- package/skills/askr-auth-access/agents/openai.yaml +0 -4
- package/skills/askr-cli-vite/SKILL.md +0 -76
- package/skills/askr-cli-vite/agents/openai.yaml +0 -4
- package/skills/askr-dashboard-charts/SKILL.md +0 -99
- package/skills/askr-dashboard-charts/agents/openai.yaml +0 -4
- package/skills/askr-design-system/SKILL.md +0 -86
- package/skills/askr-design-system/agents/openai.yaml +0 -4
- package/skills/askr-env-config/SKILL.md +0 -74
- package/skills/askr-env-config/agents/openai.yaml +0 -4
- package/skills/askr-error-loading-empty/SKILL.md +0 -89
- package/skills/askr-error-loading-empty/agents/openai.yaml +0 -4
- package/skills/askr-file-upload-artifacts/SKILL.md +0 -77
- package/skills/askr-file-upload-artifacts/agents/openai.yaml +0 -4
- package/skills/askr-forms-tables-crud/SKILL.md +0 -98
- package/skills/askr-forms-tables-crud/agents/openai.yaml +0 -4
- package/skills/askr-mental-model/SKILL.md +0 -109
- package/skills/askr-mental-model/agents/openai.yaml +0 -4
- package/skills/askr-migration-react/SKILL.md +0 -81
- package/skills/askr-migration-react/agents/openai.yaml +0 -4
- package/skills/askr-observability-debugging/SKILL.md +0 -83
- package/skills/askr-observability-debugging/agents/openai.yaml +0 -4
- package/skills/askr-project-structure/SKILL.md +0 -79
- package/skills/askr-project-structure/agents/openai.yaml +0 -4
- package/skills/askr-query-mutation/SKILL.md +0 -115
- package/skills/askr-query-mutation/agents/openai.yaml +0 -4
- package/skills/askr-realtime-streaming/SKILL.md +0 -76
- package/skills/askr-realtime-streaming/agents/openai.yaml +0 -4
- package/skills/askr-resources-data/SKILL.md +0 -100
- package/skills/askr-resources-data/agents/openai.yaml +0 -4
- package/skills/askr-routing-layouts/SKILL.md +0 -107
- package/skills/askr-routing-layouts/agents/openai.yaml +0 -4
- package/skills/askr-runtime-reactivity/SKILL.md +0 -92
- package/skills/askr-runtime-reactivity/agents/openai.yaml +0 -4
- package/skills/askr-ssr-ssg/SKILL.md +0 -76
- package/skills/askr-ssr-ssg/agents/openai.yaml +0 -4
- package/skills/askr-testing-determinism/SKILL.md +0 -75
- package/skills/askr-testing-determinism/agents/openai.yaml +0 -4
- package/skills/askr-theming/SKILL.md +0 -86
- package/skills/askr-theming/agents/openai.yaml +0 -4
- package/skills/askr-ui-composition/SKILL.md +0 -92
- package/skills/askr-ui-composition/agents/openai.yaml +0 -4
- package/templates/full-stack/AGENTS.md +0 -11
- package/templates/full-stack/README.md +0 -17
- package/templates/full-stack/gitignore.template +0 -4
- package/templates/full-stack/index.html +0 -13
- package/templates/full-stack/package.json +0 -39
- package/templates/full-stack/server.ts +0 -9
- package/templates/full-stack/src/action-authorizations.ts +0 -10
- package/templates/full-stack/src/actions/create-message.ts +0 -10
- package/templates/full-stack/src/i18n.ts +0 -8
- package/templates/full-stack/src/main.tsx +0 -11
- package/templates/full-stack/src/pages/home.tsx +0 -25
- package/templates/full-stack/src/pages/layout.tsx +0 -43
- package/templates/full-stack/src/pages/not-found.tsx +0 -7
- package/templates/full-stack/src/routes.tsx +0 -19
- package/templates/full-stack/src/schemas.ts +0 -10
- package/templates/full-stack/src/server/action-registry.ts +0 -8
- package/templates/full-stack/src/server/actions/create-message.ts +0 -14
- package/templates/full-stack/src/server/app.ts +0 -76
- package/templates/full-stack/src/server/dependencies.ts +0 -55
- package/templates/full-stack/src/server/entry-server.ts +0 -8
- package/templates/full-stack/src/telemetry.ts +0 -3
- package/templates/full-stack/src/vite-server.d.ts +0 -5
- package/templates/full-stack/tests/actions/create-message.test.ts +0 -12
- package/templates/full-stack/tsconfig.json +0 -15
- package/templates/full-stack/vite.config.ts +0 -10
- package/templates/spa/AGENTS.md +0 -60
- package/templates/spa/README.md +0 -96
- package/templates/spa/gitignore.template +0 -16
- package/templates/spa/index.html +0 -13
- package/templates/spa/package.json +0 -35
- package/templates/spa/src/adapters/operations-client.ts +0 -95
- package/templates/spa/src/components/shared/metric-card.tsx +0 -27
- package/templates/spa/src/components/shared/status-badge.tsx +0 -24
- package/templates/spa/src/features/operations/operations.query.ts +0 -9
- package/templates/spa/src/main.tsx +0 -10
- package/templates/spa/src/pages/_layout.tsx +0 -9
- package/templates/spa/src/pages/_routes.tsx +0 -27
- package/templates/spa/src/pages/app/_layout.tsx +0 -94
- package/templates/spa/src/pages/app/_routes.tsx +0 -10
- package/templates/spa/src/pages/app/admin-home.tsx +0 -170
- package/templates/spa/src/pages/app/agent-runs.tsx +0 -93
- package/templates/spa/src/pages/app/settings.tsx +0 -91
- package/templates/spa/src/pages/auth/_layout.tsx +0 -38
- package/templates/spa/src/pages/auth/_routes.tsx +0 -6
- package/templates/spa/src/pages/auth/login.tsx +0 -63
- package/templates/spa/src/pages/not-found.tsx +0 -22
- package/templates/spa/src/pages/public/_layout.tsx +0 -47
- package/templates/spa/src/pages/public/_routes.tsx +0 -6
- package/templates/spa/src/pages/public/home.tsx +0 -143
- package/templates/spa/src/shared/format.ts +0 -12
- package/templates/spa/src/shared/navigation.ts +0 -15
- package/templates/spa/src/styles/components.css +0 -49
- package/templates/spa/src/styles/layout.css +0 -192
- package/templates/spa/src/styles/reset.css +0 -44
- package/templates/spa/src/styles/theme.css +0 -57
- package/templates/spa/src/styles/tokens.css +0 -20
- package/templates/spa/src/styles.css +0 -7
- package/templates/spa/src/styles.d.ts +0 -1
- package/templates/spa/tests/app.test.tsx +0 -37
- package/templates/spa/tests/components/shared.test.tsx +0 -12
- package/templates/spa/tests/resources.test.ts +0 -22
- package/templates/spa/tsconfig.json +0 -20
- package/templates/spa/tsconfig.node.json +0 -10
- package/templates/spa/vite.config.ts +0 -24
- package/templates/spa/vitest.config.ts +0 -17
- package/templates/ssg/AGENTS.md +0 -55
- package/templates/ssg/README.md +0 -39
- package/templates/ssg/gitignore.template +0 -17
- package/templates/ssg/index.html +0 -13
- package/templates/ssg/package.json +0 -35
- package/templates/ssg/src/app.tsx +0 -14
- package/templates/ssg/src/components/badge.tsx +0 -3
- package/templates/ssg/src/components/counter.tsx +0 -30
- package/templates/ssg/src/components/site-shell.tsx +0 -111
- package/templates/ssg/src/jsx.d.ts +0 -23
- package/templates/ssg/src/main.tsx +0 -8
- package/templates/ssg/src/pages/about.tsx +0 -82
- package/templates/ssg/src/pages/content.tsx +0 -63
- package/templates/ssg/src/pages/example.tsx +0 -97
- package/templates/ssg/src/pages/home.tsx +0 -51
- package/templates/ssg/src/resources/user.ts +0 -15
- package/templates/ssg/src/routes.tsx +0 -15
- package/templates/ssg/src/styles.css +0 -89
- package/templates/ssg/src/vite-env.d.ts +0 -5
- package/templates/ssg/ssg.config.ts +0 -34
- package/templates/ssg/tests/app.test.tsx +0 -19
- package/templates/ssg/tests/components/counter.test.tsx +0 -18
- package/templates/ssg/tests/resources.test.ts +0 -11
- package/templates/ssg/tests/ssg-config.test.ts +0 -11
- package/templates/ssg/tsconfig.json +0 -20
- package/templates/ssg/tsconfig.node.json +0 -10
- package/templates/ssg/vite.config.ts +0 -24
- package/templates/ssg/vitest.config.ts +0 -17
- package/templates/ssr/AGENTS.md +0 -28
- package/templates/ssr/README.md +0 -38
- package/templates/ssr/gitignore.template +0 -16
- package/templates/ssr/index.html +0 -13
- package/templates/ssr/package.json +0 -34
- package/templates/ssr/server.ts +0 -10
- package/templates/ssr/src/app.tsx +0 -21
- package/templates/ssr/src/components/counter.tsx +0 -26
- package/templates/ssr/src/entry-server.tsx +0 -12
- package/templates/ssr/src/main.tsx +0 -11
- package/templates/ssr/src/pages/about.tsx +0 -75
- package/templates/ssr/src/pages/example.tsx +0 -113
- package/templates/ssr/src/pages/home.tsx +0 -56
- package/templates/ssr/src/resources/user.ts +0 -15
- package/templates/ssr/src/routes.tsx +0 -13
- package/templates/ssr/src/styles.css +0 -148
- package/templates/ssr/src/vite-server.d.ts +0 -7
- package/templates/ssr/tests/app.test.tsx +0 -24
- package/templates/ssr/tests/components/counter.test.tsx +0 -13
- package/templates/ssr/tests/resources.test.ts +0 -11
- package/templates/ssr/tsconfig.json +0 -22
- package/templates/ssr/tsconfig.node.json +0 -10
- package/templates/ssr/tsconfig.server.json +0 -15
- package/templates/ssr/vite.config.ts +0 -25
- package/templates/ssr/vitest.config.ts +0 -13
- package/templates/startkit/AGENTS.md +0 -77
- package/templates/startkit/README.md +0 -192
- package/templates/startkit/gitignore.template +0 -16
- package/templates/startkit/index.html +0 -13
- package/templates/startkit/package.json +0 -35
- package/templates/startkit/src/components/app-header.tsx +0 -93
- package/templates/startkit/src/components/app-sidebar.tsx +0 -95
- package/templates/startkit/src/components/data-table.tsx +0 -80
- package/templates/startkit/src/components/empty-state.tsx +0 -13
- package/templates/startkit/src/components/page-header.tsx +0 -23
- package/templates/startkit/src/components/stat-card.tsx +0 -24
- package/templates/startkit/src/features/accounts/account-filters.tsx +0 -61
- package/templates/startkit/src/features/accounts/account-table.tsx +0 -79
- package/templates/startkit/src/lib/format.ts +0 -31
- package/templates/startkit/src/lib/mock-data.ts +0 -441
- package/templates/startkit/src/lib/routes.ts +0 -116
- package/templates/startkit/src/main.tsx +0 -12
- package/templates/startkit/src/pages/_layout.tsx +0 -56
- package/templates/startkit/src/pages/auth/_layout.tsx +0 -7
- package/templates/startkit/src/pages/auth/login.tsx +0 -129
- package/templates/startkit/src/pages/home.tsx +0 -83
- package/templates/startkit/src/pages/not-found.tsx +0 -24
- package/templates/startkit/src/pages/workspace/_layout.tsx +0 -17
- package/templates/startkit/src/pages/workspace/accounts/index.tsx +0 -217
- package/templates/startkit/src/pages/workspace/dashboard.tsx +0 -118
- package/templates/startkit/src/pages/workspace/settings.tsx +0 -218
- package/templates/startkit/src/router.tsx +0 -7
- package/templates/startkit/src/routes/auth-config.ts +0 -38
- package/templates/startkit/src/routes/auth.ts +0 -8
- package/templates/startkit/src/routes/index.ts +0 -28
- package/templates/startkit/src/routes/public.ts +0 -8
- package/templates/startkit/src/routes/workspace/accounts.ts +0 -8
- package/templates/startkit/src/routes/workspace/index.ts +0 -14
- package/templates/startkit/src/styles/components.css +0 -396
- package/templates/startkit/src/styles/layout.css +0 -192
- package/templates/startkit/src/styles/reset.css +0 -38
- package/templates/startkit/src/styles/theme.css +0 -76
- package/templates/startkit/src/styles/tokens.css +0 -67
- package/templates/startkit/src/styles.css +0 -7
- package/templates/startkit/src/toast.ts +0 -55
- package/templates/startkit/src/utils/join-classes.ts +0 -4
- package/templates/startkit/src/vite-env.d.ts +0 -5
- package/templates/startkit/tests/app.test.tsx +0 -32
- package/templates/startkit/tests/preferences.test.ts +0 -43
- package/templates/startkit/tests/resources.test.ts +0 -82
- package/templates/startkit/tsconfig.json +0 -20
- package/templates/startkit/tsconfig.node.json +0 -10
- package/templates/startkit/vite.config.ts +0 -32
- package/templates/startkit/vitest.config.ts +0 -13
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { Checkbox } from '@askrjs/ui/checkbox';
|
|
2
|
-
import { Button } from '@askrjs/ui/button';
|
|
3
|
-
import { EyeIcon } from '@askrjs/lucide';
|
|
4
|
-
import DataTable, { type DataTableColumn } from '../../components/data-table';
|
|
5
|
-
import type { AccountRecord } from '../../lib/mock-data';
|
|
6
|
-
import { formatCurrency } from '../../lib/format';
|
|
7
|
-
|
|
8
|
-
export default function AccountTable(props: {
|
|
9
|
-
rows: () => AccountRecord[];
|
|
10
|
-
selectedIds: () => string[];
|
|
11
|
-
onToggleRow: (id: string) => void;
|
|
12
|
-
onOpenRow: (row: AccountRecord) => void;
|
|
13
|
-
loading?: boolean;
|
|
14
|
-
errorText?: string | null;
|
|
15
|
-
}) {
|
|
16
|
-
const columns: DataTableColumn<AccountRecord>[] = [
|
|
17
|
-
{
|
|
18
|
-
key: 'select',
|
|
19
|
-
header: 'Select',
|
|
20
|
-
class: 'cell-select',
|
|
21
|
-
render: (row) => (
|
|
22
|
-
<Checkbox
|
|
23
|
-
checked={props.selectedIds().includes(row.id)}
|
|
24
|
-
onPress={() => props.onToggleRow(row.id)}
|
|
25
|
-
aria-label={`Select ${row.name}`}
|
|
26
|
-
/>
|
|
27
|
-
),
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
key: 'account',
|
|
31
|
-
header: 'Account',
|
|
32
|
-
render: (row) => (
|
|
33
|
-
<div class="entity-copy">
|
|
34
|
-
<strong>{row.name}</strong>
|
|
35
|
-
<span class="muted">{row.email}</span>
|
|
36
|
-
</div>
|
|
37
|
-
),
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
key: 'plan',
|
|
41
|
-
header: 'Plan',
|
|
42
|
-
render: (row) => row.plan,
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
key: 'mrr',
|
|
46
|
-
header: 'MRR',
|
|
47
|
-
render: (row) => formatCurrency(row.mrr),
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
key: 'status',
|
|
51
|
-
header: 'Status',
|
|
52
|
-
render: (row) => (
|
|
53
|
-
<span class={`status-pill status-${row.status}`}>{row.status}</span>
|
|
54
|
-
),
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
key: 'actions',
|
|
58
|
-
header: 'Actions',
|
|
59
|
-
class: 'cell-actions',
|
|
60
|
-
render: (row) => (
|
|
61
|
-
<Button onPress={() => props.onOpenRow(row)}>
|
|
62
|
-
<EyeIcon size={14} aria-hidden="true" /> View
|
|
63
|
-
</Button>
|
|
64
|
-
),
|
|
65
|
-
},
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<DataTable
|
|
70
|
-
rows={props.rows}
|
|
71
|
-
rowKey={(row) => row.id}
|
|
72
|
-
columns={columns}
|
|
73
|
-
isLoading={props.loading}
|
|
74
|
-
errorText={props.errorText}
|
|
75
|
-
emptyTitle="No accounts matched"
|
|
76
|
-
emptyDescription="Try a broader search, or clear your status filter."
|
|
77
|
-
/>
|
|
78
|
-
);
|
|
79
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export function formatCurrency(value: number): string {
|
|
2
|
-
return new Intl.NumberFormat('en-US', {
|
|
3
|
-
style: 'currency',
|
|
4
|
-
currency: 'USD',
|
|
5
|
-
maximumFractionDigits: 0,
|
|
6
|
-
}).format(value);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function formatPercent(value: number): string {
|
|
10
|
-
return `${value > 0 ? '+' : ''}${value.toFixed(1)}%`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function formatRelativeDate(timestamp: string): string {
|
|
14
|
-
const date = new Date(timestamp);
|
|
15
|
-
const diffMs = Date.now() - date.getTime();
|
|
16
|
-
const hourMs = 60 * 60 * 1000;
|
|
17
|
-
const dayMs = 24 * hourMs;
|
|
18
|
-
|
|
19
|
-
if (diffMs < hourMs) {
|
|
20
|
-
const minutes = Math.max(1, Math.floor(diffMs / (60 * 1000)));
|
|
21
|
-
return `${minutes}m ago`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (diffMs < dayMs) {
|
|
25
|
-
const hours = Math.max(1, Math.floor(diffMs / hourMs));
|
|
26
|
-
return `${hours}h ago`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const days = Math.max(1, Math.floor(diffMs / dayMs));
|
|
30
|
-
return `${days}d ago`;
|
|
31
|
-
}
|
|
@@ -1,441 +0,0 @@
|
|
|
1
|
-
import { formatCurrency, formatPercent } from './format';
|
|
2
|
-
|
|
3
|
-
export type AppearanceMode = 'default' | 'harbor' | 'ink';
|
|
4
|
-
export type AccountStatus = 'active' | 'pending' | 'archived';
|
|
5
|
-
|
|
6
|
-
export type DashboardStat = {
|
|
7
|
-
key: string;
|
|
8
|
-
label: string;
|
|
9
|
-
value: string;
|
|
10
|
-
trend: string;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export type ActivityEntry = {
|
|
14
|
-
id: string;
|
|
15
|
-
actor: string;
|
|
16
|
-
action: string;
|
|
17
|
-
resource: string;
|
|
18
|
-
timestamp: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type AccountRecord = {
|
|
22
|
-
id: string;
|
|
23
|
-
name: string;
|
|
24
|
-
email: string;
|
|
25
|
-
plan: 'Starter' | 'Growth' | 'Enterprise';
|
|
26
|
-
status: AccountStatus;
|
|
27
|
-
mrr: number;
|
|
28
|
-
seats: number;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type PagedResult<T> = {
|
|
32
|
-
items: T[];
|
|
33
|
-
totalItems: number;
|
|
34
|
-
totalPages: number;
|
|
35
|
-
page: number;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const appearanceStorageKey = 'startkit:appearance';
|
|
39
|
-
const sessionStorageKey = 'startkit:session';
|
|
40
|
-
const appearanceOrder: AppearanceMode[] = ['default', 'harbor', 'ink'];
|
|
41
|
-
const storageFallback = new Map<string, string>();
|
|
42
|
-
|
|
43
|
-
let appearance: AppearanceMode = 'default';
|
|
44
|
-
let sessionEmail: string | null = null;
|
|
45
|
-
|
|
46
|
-
const accountSeed: AccountRecord[] = [
|
|
47
|
-
{
|
|
48
|
-
id: 'acc_001',
|
|
49
|
-
name: 'Northwind Logistics',
|
|
50
|
-
email: 'ops@northwind.example',
|
|
51
|
-
plan: 'Enterprise',
|
|
52
|
-
status: 'active',
|
|
53
|
-
mrr: 32000,
|
|
54
|
-
seats: 82,
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: 'acc_002',
|
|
58
|
-
name: 'Pinecone Retail',
|
|
59
|
-
email: 'team@pinecone.example',
|
|
60
|
-
plan: 'Growth',
|
|
61
|
-
status: 'active',
|
|
62
|
-
mrr: 9800,
|
|
63
|
-
seats: 28,
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
id: 'acc_003',
|
|
67
|
-
name: 'Hexa Health',
|
|
68
|
-
email: 'care@hexa.example',
|
|
69
|
-
plan: 'Growth',
|
|
70
|
-
status: 'pending',
|
|
71
|
-
mrr: 6400,
|
|
72
|
-
seats: 16,
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
id: 'acc_004',
|
|
76
|
-
name: 'Atlas Research',
|
|
77
|
-
email: 'lab@atlas.example',
|
|
78
|
-
plan: 'Starter',
|
|
79
|
-
status: 'archived',
|
|
80
|
-
mrr: 0,
|
|
81
|
-
seats: 4,
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
id: 'acc_005',
|
|
85
|
-
name: 'Lumen Ops',
|
|
86
|
-
email: 'admin@lumen.example',
|
|
87
|
-
plan: 'Growth',
|
|
88
|
-
status: 'active',
|
|
89
|
-
mrr: 11400,
|
|
90
|
-
seats: 34,
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
id: 'acc_006',
|
|
94
|
-
name: 'Summit Security',
|
|
95
|
-
email: 'it@summit.example',
|
|
96
|
-
plan: 'Enterprise',
|
|
97
|
-
status: 'pending',
|
|
98
|
-
mrr: 22900,
|
|
99
|
-
seats: 63,
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
id: 'acc_007',
|
|
103
|
-
name: 'Aster Finance',
|
|
104
|
-
email: 'hello@aster.example',
|
|
105
|
-
plan: 'Starter',
|
|
106
|
-
status: 'active',
|
|
107
|
-
mrr: 3700,
|
|
108
|
-
seats: 11,
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
id: 'acc_008',
|
|
112
|
-
name: 'Calico Labs',
|
|
113
|
-
email: 'team@calico.example',
|
|
114
|
-
plan: 'Growth',
|
|
115
|
-
status: 'active',
|
|
116
|
-
mrr: 12400,
|
|
117
|
-
seats: 39,
|
|
118
|
-
},
|
|
119
|
-
];
|
|
120
|
-
|
|
121
|
-
let accountsDb = [...accountSeed];
|
|
122
|
-
|
|
123
|
-
const activitiesSeed: ActivityEntry[] = [
|
|
124
|
-
{
|
|
125
|
-
id: 'evt_001',
|
|
126
|
-
actor: 'Alex Morgan',
|
|
127
|
-
action: 'invited',
|
|
128
|
-
resource: '2 teammates to Northwind Logistics',
|
|
129
|
-
timestamp: new Date(Date.now() - 16 * 60 * 1000).toISOString(),
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
id: 'evt_002',
|
|
133
|
-
actor: 'Priya Shah',
|
|
134
|
-
action: 'upgraded',
|
|
135
|
-
resource: 'Pinecone Retail from Starter to Growth',
|
|
136
|
-
timestamp: new Date(Date.now() - 74 * 60 * 1000).toISOString(),
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
id: 'evt_003',
|
|
140
|
-
actor: 'Jordan Kim',
|
|
141
|
-
action: 'resolved',
|
|
142
|
-
resource: 'billing retry for Aster Finance',
|
|
143
|
-
timestamp: new Date(Date.now() - 5.5 * 60 * 60 * 1000).toISOString(),
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
id: 'evt_004',
|
|
147
|
-
actor: 'System',
|
|
148
|
-
action: 'synced',
|
|
149
|
-
resource: 'daily usage metrics',
|
|
150
|
-
timestamp: new Date(Date.now() - 26 * 60 * 60 * 1000).toISOString(),
|
|
151
|
-
},
|
|
152
|
-
];
|
|
153
|
-
|
|
154
|
-
function wait(signal?: AbortSignal, delay = 220): Promise<void> {
|
|
155
|
-
return new Promise((resolve, reject) => {
|
|
156
|
-
const timeout = setTimeout(resolve, delay);
|
|
157
|
-
|
|
158
|
-
if (!signal) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const onAbort = () => {
|
|
163
|
-
clearTimeout(timeout);
|
|
164
|
-
reject(new Error('Request aborted'));
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
if (signal.aborted) {
|
|
168
|
-
onAbort();
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function readStorage(key: string): string | null {
|
|
177
|
-
const fallback = storageFallback.get(key) ?? null;
|
|
178
|
-
|
|
179
|
-
if (typeof window === 'undefined') {
|
|
180
|
-
return fallback;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
const storage = window.localStorage as {
|
|
185
|
-
getItem?: (storageKey: string) => string | null;
|
|
186
|
-
};
|
|
187
|
-
if (typeof storage.getItem !== 'function') {
|
|
188
|
-
return fallback;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return storage.getItem(key) ?? fallback;
|
|
192
|
-
} catch {
|
|
193
|
-
return fallback;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function writeStorage(key: string, value: string) {
|
|
198
|
-
storageFallback.set(key, value);
|
|
199
|
-
|
|
200
|
-
if (typeof window === 'undefined') {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
const storage = window.localStorage as {
|
|
206
|
-
setItem?: (storageKey: string, storageValue: string) => void;
|
|
207
|
-
};
|
|
208
|
-
if (typeof storage.setItem !== 'function') {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
storage.setItem(key, value);
|
|
213
|
-
} catch {
|
|
214
|
-
// Ignore storage write failures in non-browser or restricted test envs.
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function removeStorage(key: string) {
|
|
219
|
-
storageFallback.delete(key);
|
|
220
|
-
|
|
221
|
-
if (typeof window === 'undefined') {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
const storage = window.localStorage as {
|
|
227
|
-
removeItem?: (storageKey: string) => void;
|
|
228
|
-
};
|
|
229
|
-
if (typeof storage.removeItem !== 'function') {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
storage.removeItem(key);
|
|
234
|
-
} catch {
|
|
235
|
-
// Ignore storage delete failures in non-browser or restricted test envs.
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export function initializeAppSession() {
|
|
240
|
-
if (typeof window === 'undefined') {
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const storedAppearance = normalizeAppearance(
|
|
245
|
-
readStorage(appearanceStorageKey) ?? appearance
|
|
246
|
-
);
|
|
247
|
-
setAppearance(storedAppearance);
|
|
248
|
-
|
|
249
|
-
sessionEmail = readStorage(sessionStorageKey) ?? sessionEmail;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export function appearanceMode() {
|
|
253
|
-
return appearance;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export function setAppearance(mode: AppearanceMode) {
|
|
257
|
-
const normalized = normalizeAppearance(mode);
|
|
258
|
-
appearance = normalized;
|
|
259
|
-
|
|
260
|
-
if (typeof document !== 'undefined') {
|
|
261
|
-
document.documentElement.dataset.skAppearance = normalized;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
writeStorage(appearanceStorageKey, normalized);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
export function nextAppearance(mode: AppearanceMode): AppearanceMode {
|
|
268
|
-
const current = normalizeAppearance(mode);
|
|
269
|
-
const index = appearanceOrder.indexOf(current);
|
|
270
|
-
return appearanceOrder[(index + 1) % appearanceOrder.length];
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
export function resetAppearancePreference() {
|
|
274
|
-
appearance = 'default';
|
|
275
|
-
|
|
276
|
-
if (typeof document !== 'undefined') {
|
|
277
|
-
delete document.documentElement.dataset.skAppearance;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
removeStorage(appearanceStorageKey);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export function isAuthenticated(): boolean {
|
|
284
|
-
return sessionEmail !== null;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
export function getSessionEmail(): string | null {
|
|
288
|
-
return sessionEmail;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
export async function signIn(input: {
|
|
292
|
-
email: string;
|
|
293
|
-
password: string;
|
|
294
|
-
signal?: AbortSignal;
|
|
295
|
-
}): Promise<void> {
|
|
296
|
-
await wait(input.signal, 260);
|
|
297
|
-
|
|
298
|
-
const email = input.email.trim().toLowerCase();
|
|
299
|
-
if (!email.includes('@')) {
|
|
300
|
-
throw new Error('Enter a valid email address.');
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (input.password.trim().length < 8) {
|
|
304
|
-
throw new Error('Password must be at least 8 characters.');
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
sessionEmail = email;
|
|
308
|
-
|
|
309
|
-
writeStorage(sessionStorageKey, email);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
export function signOut() {
|
|
313
|
-
sessionEmail = null;
|
|
314
|
-
|
|
315
|
-
removeStorage(sessionStorageKey);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export async function getDashboardData(input: {
|
|
319
|
-
signal?: AbortSignal;
|
|
320
|
-
}): Promise<{ stats: DashboardStat[]; activities: ActivityEntry[] }> {
|
|
321
|
-
await wait(input.signal, 240);
|
|
322
|
-
|
|
323
|
-
const activeAccounts = accountsDb.filter(
|
|
324
|
-
(item) => item.status === 'active'
|
|
325
|
-
).length;
|
|
326
|
-
const pendingAccounts = accountsDb.filter(
|
|
327
|
-
(item) => item.status === 'pending'
|
|
328
|
-
).length;
|
|
329
|
-
const mrr = accountsDb.reduce((sum, item) => sum + item.mrr, 0);
|
|
330
|
-
const seats = accountsDb.reduce((sum, item) => sum + item.seats, 0);
|
|
331
|
-
|
|
332
|
-
return {
|
|
333
|
-
stats: [
|
|
334
|
-
{
|
|
335
|
-
key: 'mrr',
|
|
336
|
-
label: 'Monthly recurring revenue',
|
|
337
|
-
value: formatCurrency(mrr),
|
|
338
|
-
trend: formatPercent(4.2),
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
key: 'accounts',
|
|
342
|
-
label: 'Active accounts',
|
|
343
|
-
value: String(activeAccounts),
|
|
344
|
-
trend: formatPercent(2.3),
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
key: 'pending',
|
|
348
|
-
label: 'Pending approvals',
|
|
349
|
-
value: String(pendingAccounts),
|
|
350
|
-
trend: formatPercent(-1.1),
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
key: 'seats',
|
|
354
|
-
label: 'Provisioned seats',
|
|
355
|
-
value: String(seats),
|
|
356
|
-
trend: formatPercent(3.8),
|
|
357
|
-
},
|
|
358
|
-
],
|
|
359
|
-
activities: [...activitiesSeed],
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
export async function listAccounts(input: {
|
|
364
|
-
query: string;
|
|
365
|
-
status: AccountStatus | 'all';
|
|
366
|
-
page: number;
|
|
367
|
-
pageSize: number;
|
|
368
|
-
signal?: AbortSignal;
|
|
369
|
-
}): Promise<PagedResult<AccountRecord>> {
|
|
370
|
-
await wait(input.signal, 280);
|
|
371
|
-
|
|
372
|
-
if (input.query.trim().toLowerCase() === 'error') {
|
|
373
|
-
throw new Error('Mock API failed. Clear search text "error" and retry.');
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const normalizedQuery = input.query.trim().toLowerCase();
|
|
377
|
-
const filtered = accountsDb.filter((item) => {
|
|
378
|
-
const statusMatch =
|
|
379
|
-
input.status === 'all' ? true : item.status === input.status;
|
|
380
|
-
if (!statusMatch) {
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (!normalizedQuery) {
|
|
385
|
-
return true;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
return (
|
|
389
|
-
item.name.toLowerCase().includes(normalizedQuery) ||
|
|
390
|
-
item.email.toLowerCase().includes(normalizedQuery) ||
|
|
391
|
-
item.id.toLowerCase().includes(normalizedQuery)
|
|
392
|
-
);
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
const totalItems = filtered.length;
|
|
396
|
-
const totalPages = Math.max(1, Math.ceil(totalItems / input.pageSize));
|
|
397
|
-
const page = Math.min(Math.max(1, input.page), totalPages);
|
|
398
|
-
const start = (page - 1) * input.pageSize;
|
|
399
|
-
|
|
400
|
-
return {
|
|
401
|
-
items: filtered.slice(start, start + input.pageSize),
|
|
402
|
-
totalItems,
|
|
403
|
-
totalPages,
|
|
404
|
-
page,
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
export async function archiveAccounts(input: {
|
|
409
|
-
ids: string[];
|
|
410
|
-
signal?: AbortSignal;
|
|
411
|
-
}): Promise<{ archived: number }> {
|
|
412
|
-
await wait(input.signal, 220);
|
|
413
|
-
|
|
414
|
-
let archived = 0;
|
|
415
|
-
accountsDb = accountsDb.map((item) => {
|
|
416
|
-
if (!input.ids.includes(item.id)) {
|
|
417
|
-
return item;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
archived += 1;
|
|
421
|
-
return {
|
|
422
|
-
...item,
|
|
423
|
-
status: 'archived',
|
|
424
|
-
};
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
return { archived };
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
export function resetMockData() {
|
|
431
|
-
accountsDb = [...accountSeed];
|
|
432
|
-
signOut();
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function normalizeAppearance(value: string | null | undefined): AppearanceMode {
|
|
436
|
-
if (value === 'harbor' || value === 'ink' || value === 'default') {
|
|
437
|
-
return value;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return 'default';
|
|
441
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
export type AppRouteKind = 'public' | 'auth' | 'protected';
|
|
2
|
-
|
|
3
|
-
export type AppRouteDefinition = {
|
|
4
|
-
id: 'landing' | 'login' | 'dashboard' | 'accounts' | 'settings';
|
|
5
|
-
href: string;
|
|
6
|
-
label: string;
|
|
7
|
-
navLabel: string;
|
|
8
|
-
description: string;
|
|
9
|
-
kind: AppRouteKind;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
const appOrigin = 'https://{{appName}}.local';
|
|
13
|
-
|
|
14
|
-
export const landingRoute: AppRouteDefinition = {
|
|
15
|
-
id: 'landing',
|
|
16
|
-
href: '/',
|
|
17
|
-
label: 'Home',
|
|
18
|
-
navLabel: 'Home',
|
|
19
|
-
description: 'Marketing overview and entry points into the starter app.',
|
|
20
|
-
kind: 'public',
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const loginRoute: AppRouteDefinition = {
|
|
24
|
-
id: 'login',
|
|
25
|
-
href: '/login',
|
|
26
|
-
label: 'Login',
|
|
27
|
-
navLabel: 'Sign in',
|
|
28
|
-
description: 'Auth entry for the starter app.',
|
|
29
|
-
kind: 'auth',
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export const dashboardRoute: AppRouteDefinition = {
|
|
33
|
-
id: 'dashboard',
|
|
34
|
-
href: '/dashboard',
|
|
35
|
-
label: 'Dashboard',
|
|
36
|
-
navLabel: 'Dashboard',
|
|
37
|
-
description: 'Overview of growth, usage, and recent workspace activity.',
|
|
38
|
-
kind: 'protected',
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export const accountsRoute: AppRouteDefinition = {
|
|
42
|
-
id: 'accounts',
|
|
43
|
-
href: '/accounts',
|
|
44
|
-
label: 'Accounts',
|
|
45
|
-
navLabel: 'Accounts',
|
|
46
|
-
description: 'Search, filter, and manage account records.',
|
|
47
|
-
kind: 'protected',
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export const settingsRoute: AppRouteDefinition = {
|
|
51
|
-
id: 'settings',
|
|
52
|
-
href: '/settings',
|
|
53
|
-
label: 'Settings',
|
|
54
|
-
navLabel: 'Settings',
|
|
55
|
-
description: 'Profile fields, preferences, and modal patterns.',
|
|
56
|
-
kind: 'protected',
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export const defaultProtectedRoute = dashboardRoute;
|
|
60
|
-
export const protectedRoutes = [
|
|
61
|
-
dashboardRoute,
|
|
62
|
-
accountsRoute,
|
|
63
|
-
settingsRoute,
|
|
64
|
-
] as const;
|
|
65
|
-
export const workspaceRoutes = [...protectedRoutes] as const;
|
|
66
|
-
export const routeCatalog = [
|
|
67
|
-
landingRoute,
|
|
68
|
-
loginRoute,
|
|
69
|
-
...protectedRoutes,
|
|
70
|
-
] as const;
|
|
71
|
-
|
|
72
|
-
const routeByPath = new Map(routeCatalog.map((route) => [route.href, route]));
|
|
73
|
-
|
|
74
|
-
function parseRouteTarget(target: string): URL | null {
|
|
75
|
-
try {
|
|
76
|
-
return new URL(target, appOrigin);
|
|
77
|
-
} catch {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function isProtectedRoute(pathname: string): boolean {
|
|
83
|
-
return protectedRoutes.some((route) => route.href === pathname);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function normalizeProtectedRouteTarget(
|
|
87
|
-
target: string | null | undefined
|
|
88
|
-
): string {
|
|
89
|
-
if (!target) {
|
|
90
|
-
return defaultProtectedRoute.href;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const parsed = parseRouteTarget(target);
|
|
94
|
-
if (
|
|
95
|
-
!parsed ||
|
|
96
|
-
parsed.origin !== appOrigin ||
|
|
97
|
-
!isProtectedRoute(parsed.pathname)
|
|
98
|
-
) {
|
|
99
|
-
return defaultProtectedRoute.href;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function buildLoginHref(nextTarget?: string | null): string {
|
|
106
|
-
if (!nextTarget) {
|
|
107
|
-
return loginRoute.href;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const target = normalizeProtectedRouteTarget(nextTarget);
|
|
111
|
-
return `${loginRoute.href}?next=${encodeURIComponent(target)}`;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function getRouteLabel(pathname: string): string {
|
|
115
|
-
return routeByPath.get(pathname)?.label ?? 'Workspace';
|
|
116
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { createSPA } from '@askrjs/askr/boot';
|
|
2
|
-
import { initializeAppSession } from './lib/mock-data';
|
|
3
|
-
import { pageRegistry } from './router';
|
|
4
|
-
|
|
5
|
-
initializeAppSession();
|
|
6
|
-
|
|
7
|
-
// createSPA is async: it drains lazy() chunks, applies the registry,
|
|
8
|
-
// resolves the initial route, and wires history navigation — all in one call.
|
|
9
|
-
const root = document.getElementById('app');
|
|
10
|
-
if (!root) throw new Error('Missing #app root element.');
|
|
11
|
-
|
|
12
|
-
await createSPA({ root, registry: pageRegistry });
|