commit 7a5cacc54cb6014ff80ed8b1f25aa0f5d8a0bce8 Author: Christian LeDoux Date: Tue Jul 7 20:42:46 2026 -0400 Scaffold CommiTea: yarn workspaces, Electron shell, core label schema, design system - apps/desktop: electron-vite + React + Tailwind mapped onto design tokens (preflight off; tokens/base.css owns the reset); boots to a Reginald placeholder proving fonts/tokens/core wiring - packages/core: pure TS; gitea label schema (est/*, p/*, deadline/hard) with pessimistic conflict resolution + 15 unit tests - docs/design: full design handoff (tokens, 16 component contracts, interactive 14-screen prototype, Reginald voice rules) - docs/PLAN.md: product plan (purity rule, pm-state repo, deterministic scheduler + Monte Carlo, directive log) - Deliberate deviation from novelpad stack: no ElectricSQL/PGlite — local store is a rebuildable cache over gitea REST/webhooks (better-sqlite3 in main process, arriving in P1) Co-Authored-By: Claude Fable 5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5065492 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +node_modules/ +dist/ +out/ +*.log +.DS_Store +.env +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..4ef66b1 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# CommiTea + +AI project manager built on Gitea. A deterministic scheduler does the math +(Monte Carlo forecasts over your own estimate-vs-actual history); the agent — +Reginald — captures work via interview, negotiates priorities, and explains the +consequences. Gitea holds human-authored intent; a `pm-state` repo holds +machine-derived state. The LLM never does math. + +## Structure + +- `apps/desktop` — Electron app (electron-vite, React, Tailwind) +- `packages/core` — pure TypeScript: label schema, scheduler, gitea client (no + Electron imports) +- `docs/PLAN.md` — product plan (goals, phases, not-doing list) +- `docs/design/` — design handoff: tokens, component contracts, interactive + prototype (`docs/design/ui_kits/app/index.html` via a static server), brand + voice rules in `design_system_readme.md` + +## Development + +```sh +yarn # install +yarn dev # electron app, logs tee to apps/desktop/desktop.log +yarn test # unit tests (vitest) +yarn typecheck +``` + +## Conventions + +- Yarn 4 workspaces; ESM everywhere; `.js` extensions on relative imports +- `@commitea/core` stays pure — unit-testable without Electron or network +- Design tokens are the source of truth (`apps/desktop/src/renderer/src/design/`, + mirrored from `docs/design/`); Tailwind maps onto the CSS custom properties, + never redefines them +- Reginald's voice rules live in `docs/design/design_system_readme.md` — no + emoji, no point-date forecasts, wit in sentences never in buttons diff --git a/apps/desktop/electron.vite.config.ts b/apps/desktop/electron.vite.config.ts new file mode 100644 index 0000000..7cc4946 --- /dev/null +++ b/apps/desktop/electron.vite.config.ts @@ -0,0 +1,10 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'electron-vite' + +export default defineConfig({ + main: {}, + preload: {}, + renderer: { + plugins: [react()], + }, +}) diff --git a/apps/desktop/package.json b/apps/desktop/package.json new file mode 100644 index 0000000..5f128b0 --- /dev/null +++ b/apps/desktop/package.json @@ -0,0 +1,30 @@ +{ + "name": "@commitea/desktop", + "private": true, + "type": "module", + "main": "./out/main/index.js", + "scripts": { + "dev": "electron-vite dev 2>&1 | tee desktop.log", + "build": "electron-vite build", + "start": "electron-vite preview", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@commitea/core": "workspace:*", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/node": "^22.13.1", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "electron": "^34.0.0", + "electron-vite": "^3.1.0", + "postcss": "^8.5.1", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.3", + "vite": "^6.1.0" + } +} diff --git a/apps/desktop/postcss.config.js b/apps/desktop/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/apps/desktop/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts new file mode 100644 index 0000000..c8b9573 --- /dev/null +++ b/apps/desktop/src/main/index.ts @@ -0,0 +1,44 @@ +import { join } from 'node:path' + +import { BrowserWindow, app, shell } from 'electron' + +function createWindow(): void { + const win = new BrowserWindow({ + width: 1440, + height: 900, + minWidth: 1080, + minHeight: 700, + show: false, + autoHideMenuBar: true, + backgroundColor: '#F0F4F0', + webPreferences: { + preload: join(import.meta.dirname, '../preload/index.mjs'), + sandbox: false, + }, + }) + + win.on('ready-to-show', () => win.show()) + + win.webContents.setWindowOpenHandler(({ url }) => { + void shell.openExternal(url) + return { action: 'deny' } + }) + + if (process.env.ELECTRON_RENDERER_URL) { + void win.loadURL(process.env.ELECTRON_RENDERER_URL) + } else { + void win.loadFile(join(import.meta.dirname, '../renderer/index.html')) + } +} + +void app.whenReady().then(() => { + createWindow() + + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) createWindow() + }) +}) + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') app.quit() +}) diff --git a/apps/desktop/src/preload/index.ts b/apps/desktop/src/preload/index.ts new file mode 100644 index 0000000..cf4a6ed --- /dev/null +++ b/apps/desktop/src/preload/index.ts @@ -0,0 +1,9 @@ +import { contextBridge } from 'electron' + +const api = { + platform: process.platform, +} + +export type CommiteaApi = typeof api + +contextBridge.exposeInMainWorld('commitea', api) diff --git a/apps/desktop/src/renderer/index.html b/apps/desktop/src/renderer/index.html new file mode 100644 index 0000000..e6dcbaa --- /dev/null +++ b/apps/desktop/src/renderer/index.html @@ -0,0 +1,16 @@ + + + + + CommiTea + + + + +
+ + + diff --git a/apps/desktop/src/renderer/src/app.tsx b/apps/desktop/src/renderer/src/app.tsx new file mode 100644 index 0000000..ef35ec2 --- /dev/null +++ b/apps/desktop/src/renderer/src/app.tsx @@ -0,0 +1,26 @@ +import { extractLabelFacts } from '@commitea/core' + +const facts = extractLabelFacts(['est/3d', 'p/2', 'deadline/hard']) + +export function App() { + return ( +
+
+
+

CommiTea

+

+ scaffold · phase 0 · gitea connection pending +

+
+

+ Good morning. The scaffold stands and the kettle is on, but I have nothing to manage + yet. Connect me to gitea and we shall put the pot to work. +

+

+ label parse check: est/{facts.estimateDays}d · p/{facts.priority} · hard{' '} + {String(facts.hardDeadline)} +

+
+
+ ) +} diff --git a/apps/desktop/src/renderer/src/design/assets/fonts/caslon-display-normal-400.woff2 b/apps/desktop/src/renderer/src/design/assets/fonts/caslon-display-normal-400.woff2 new file mode 100644 index 0000000..ad36393 Binary files /dev/null and b/apps/desktop/src/renderer/src/design/assets/fonts/caslon-display-normal-400.woff2 differ diff --git a/apps/desktop/src/renderer/src/design/assets/fonts/caslon-text-italic-400.woff2 b/apps/desktop/src/renderer/src/design/assets/fonts/caslon-text-italic-400.woff2 new file mode 100644 index 0000000..b098c59 Binary files /dev/null and b/apps/desktop/src/renderer/src/design/assets/fonts/caslon-text-italic-400.woff2 differ diff --git a/apps/desktop/src/renderer/src/design/assets/fonts/caslon-text-normal-400.woff2 b/apps/desktop/src/renderer/src/design/assets/fonts/caslon-text-normal-400.woff2 new file mode 100644 index 0000000..3850132 Binary files /dev/null and b/apps/desktop/src/renderer/src/design/assets/fonts/caslon-text-normal-400.woff2 differ diff --git a/apps/desktop/src/renderer/src/design/assets/fonts/caslon-text-normal-700.woff2 b/apps/desktop/src/renderer/src/design/assets/fonts/caslon-text-normal-700.woff2 new file mode 100644 index 0000000..70f4b22 Binary files /dev/null and b/apps/desktop/src/renderer/src/design/assets/fonts/caslon-text-normal-700.woff2 differ diff --git a/apps/desktop/src/renderer/src/design/assets/fonts/instrument-sans-italic-400-700.woff2 b/apps/desktop/src/renderer/src/design/assets/fonts/instrument-sans-italic-400-700.woff2 new file mode 100644 index 0000000..b4f9b28 Binary files /dev/null and b/apps/desktop/src/renderer/src/design/assets/fonts/instrument-sans-italic-400-700.woff2 differ diff --git a/apps/desktop/src/renderer/src/design/assets/fonts/instrument-sans-normal-400-700.woff2 b/apps/desktop/src/renderer/src/design/assets/fonts/instrument-sans-normal-400-700.woff2 new file mode 100644 index 0000000..665fa65 Binary files /dev/null and b/apps/desktop/src/renderer/src/design/assets/fonts/instrument-sans-normal-400-700.woff2 differ diff --git a/apps/desktop/src/renderer/src/design/assets/fonts/plex-mono-normal-400.woff2 b/apps/desktop/src/renderer/src/design/assets/fonts/plex-mono-normal-400.woff2 new file mode 100644 index 0000000..52b6c75 Binary files /dev/null and b/apps/desktop/src/renderer/src/design/assets/fonts/plex-mono-normal-400.woff2 differ diff --git a/apps/desktop/src/renderer/src/design/assets/fonts/plex-mono-normal-500.woff2 b/apps/desktop/src/renderer/src/design/assets/fonts/plex-mono-normal-500.woff2 new file mode 100644 index 0000000..3308bce Binary files /dev/null and b/apps/desktop/src/renderer/src/design/assets/fonts/plex-mono-normal-500.woff2 differ diff --git a/apps/desktop/src/renderer/src/design/assets/fonts/plex-mono-normal-600.woff2 b/apps/desktop/src/renderer/src/design/assets/fonts/plex-mono-normal-600.woff2 new file mode 100644 index 0000000..c6759ef Binary files /dev/null and b/apps/desktop/src/renderer/src/design/assets/fonts/plex-mono-normal-600.woff2 differ diff --git a/apps/desktop/src/renderer/src/design/assets/icons/activity.svg b/apps/desktop/src/renderer/src/design/assets/icons/activity.svg new file mode 100644 index 0000000..cf3f8d4 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/activity.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/arrow-left.svg b/apps/desktop/src/renderer/src/design/assets/icons/arrow-left.svg new file mode 100644 index 0000000..ca61f09 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/arrow-left.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/arrow-right.svg b/apps/desktop/src/renderer/src/design/assets/icons/arrow-right.svg new file mode 100644 index 0000000..314b2cd --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/arrow-right.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/arrow-up-right.svg b/apps/desktop/src/renderer/src/design/assets/icons/arrow-up-right.svg new file mode 100644 index 0000000..ae714d6 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/arrow-up-right.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/bell.svg b/apps/desktop/src/renderer/src/design/assets/icons/bell.svg new file mode 100644 index 0000000..8283cf6 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/bell.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/calendar.svg b/apps/desktop/src/renderer/src/design/assets/icons/calendar.svg new file mode 100644 index 0000000..d7f82e7 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/calendar.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/chart-line.svg b/apps/desktop/src/renderer/src/design/assets/icons/chart-line.svg new file mode 100644 index 0000000..5f50974 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/chart-line.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/chart-no-axes-gantt.svg b/apps/desktop/src/renderer/src/design/assets/icons/chart-no-axes-gantt.svg new file mode 100644 index 0000000..118bf50 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/chart-no-axes-gantt.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/check.svg b/apps/desktop/src/renderer/src/design/assets/icons/check.svg new file mode 100644 index 0000000..92f4df3 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/check.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/chevron-down.svg b/apps/desktop/src/renderer/src/design/assets/icons/chevron-down.svg new file mode 100644 index 0000000..b627264 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/chevron-down.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/chevron-left.svg b/apps/desktop/src/renderer/src/design/assets/icons/chevron-left.svg new file mode 100644 index 0000000..c99c66b --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/chevron-left.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/chevron-right.svg b/apps/desktop/src/renderer/src/design/assets/icons/chevron-right.svg new file mode 100644 index 0000000..538a173 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/chevron-right.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/chevron-up.svg b/apps/desktop/src/renderer/src/design/assets/icons/chevron-up.svg new file mode 100644 index 0000000..efeec90 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/chevron-up.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/circle-alert.svg b/apps/desktop/src/renderer/src/design/assets/icons/circle-alert.svg new file mode 100644 index 0000000..61e2a97 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/circle-alert.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/circle-check.svg b/apps/desktop/src/renderer/src/design/assets/icons/circle-check.svg new file mode 100644 index 0000000..2783d3b --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/circle-check.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/circle-dashed.svg b/apps/desktop/src/renderer/src/design/assets/icons/circle-dashed.svg new file mode 100644 index 0000000..eb1e1c9 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/circle-dashed.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/circle-dot.svg b/apps/desktop/src/renderer/src/design/assets/icons/circle-dot.svg new file mode 100644 index 0000000..70ae1b3 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/circle-dot.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/circle.svg b/apps/desktop/src/renderer/src/design/assets/icons/circle.svg new file mode 100644 index 0000000..86d05a4 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/clock-3.svg b/apps/desktop/src/renderer/src/design/assets/icons/clock-3.svg new file mode 100644 index 0000000..8d752c3 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/clock-3.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/clock.svg b/apps/desktop/src/renderer/src/design/assets/icons/clock.svg new file mode 100644 index 0000000..5581397 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/clock.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/coffee.svg b/apps/desktop/src/renderer/src/design/assets/icons/coffee.svg new file mode 100644 index 0000000..83944ea --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/coffee.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/copy.svg b/apps/desktop/src/renderer/src/design/assets/icons/copy.svg new file mode 100644 index 0000000..b0e5a27 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/copy.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/ellipsis.svg b/apps/desktop/src/renderer/src/design/assets/icons/ellipsis.svg new file mode 100644 index 0000000..a3ca7d4 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/ellipsis.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/external-link.svg b/apps/desktop/src/renderer/src/design/assets/icons/external-link.svg new file mode 100644 index 0000000..a9bb97d --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/external-link.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/eye.svg b/apps/desktop/src/renderer/src/design/assets/icons/eye.svg new file mode 100644 index 0000000..182cbd4 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/eye.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/filter.svg b/apps/desktop/src/renderer/src/design/assets/icons/filter.svg new file mode 100644 index 0000000..24dc440 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/filter.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/flag.svg b/apps/desktop/src/renderer/src/design/assets/icons/flag.svg new file mode 100644 index 0000000..d24c6d1 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/flag.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/gauge.svg b/apps/desktop/src/renderer/src/design/assets/icons/gauge.svg new file mode 100644 index 0000000..34f5aac --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/gauge.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/git-branch.svg b/apps/desktop/src/renderer/src/design/assets/icons/git-branch.svg new file mode 100644 index 0000000..3b6b9a0 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/git-branch.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/git-commit-horizontal.svg b/apps/desktop/src/renderer/src/design/assets/icons/git-commit-horizontal.svg new file mode 100644 index 0000000..7cd97cc --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/git-commit-horizontal.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/git-merge.svg b/apps/desktop/src/renderer/src/design/assets/icons/git-merge.svg new file mode 100644 index 0000000..5a656c3 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/git-merge.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/git-pull-request.svg b/apps/desktop/src/renderer/src/design/assets/icons/git-pull-request.svg new file mode 100644 index 0000000..ce1791a --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/git-pull-request.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/history.svg b/apps/desktop/src/renderer/src/design/assets/icons/history.svg new file mode 100644 index 0000000..965a7b5 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/history.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/inbox.svg b/apps/desktop/src/renderer/src/design/assets/icons/inbox.svg new file mode 100644 index 0000000..6bbfd52 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/inbox.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/info.svg b/apps/desktop/src/renderer/src/design/assets/icons/info.svg new file mode 100644 index 0000000..3399cf4 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/info.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/keyboard.svg b/apps/desktop/src/renderer/src/design/assets/icons/keyboard.svg new file mode 100644 index 0000000..b54d0cb --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/keyboard.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/layers.svg b/apps/desktop/src/renderer/src/design/assets/icons/layers.svg new file mode 100644 index 0000000..e5608f9 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/layers.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/leaf.svg b/apps/desktop/src/renderer/src/design/assets/icons/leaf.svg new file mode 100644 index 0000000..60b534d --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/leaf.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/link.svg b/apps/desktop/src/renderer/src/design/assets/icons/link.svg new file mode 100644 index 0000000..cddf8de --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/link.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/list-filter.svg b/apps/desktop/src/renderer/src/design/assets/icons/list-filter.svg new file mode 100644 index 0000000..697be73 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/list-filter.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/list.svg b/apps/desktop/src/renderer/src/design/assets/icons/list.svg new file mode 100644 index 0000000..b6b3e43 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/list.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/loader-circle.svg b/apps/desktop/src/renderer/src/design/assets/icons/loader-circle.svg new file mode 100644 index 0000000..20279ce --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/loader-circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/message-square.svg b/apps/desktop/src/renderer/src/design/assets/icons/message-square.svg new file mode 100644 index 0000000..383403f --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/message-square.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/milestone.svg b/apps/desktop/src/renderer/src/design/assets/icons/milestone.svg new file mode 100644 index 0000000..4333f7f --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/milestone.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/minus.svg b/apps/desktop/src/renderer/src/design/assets/icons/minus.svg new file mode 100644 index 0000000..e941e5f --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/minus.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/moon.svg b/apps/desktop/src/renderer/src/design/assets/icons/moon.svg new file mode 100644 index 0000000..3ef47ba --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/moon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/network.svg b/apps/desktop/src/renderer/src/design/assets/icons/network.svg new file mode 100644 index 0000000..3b3f280 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/network.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/panel-left.svg b/apps/desktop/src/renderer/src/design/assets/icons/panel-left.svg new file mode 100644 index 0000000..81990ea --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/panel-left.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/pause.svg b/apps/desktop/src/renderer/src/design/assets/icons/pause.svg new file mode 100644 index 0000000..0c830f8 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/pause.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/pencil.svg b/apps/desktop/src/renderer/src/design/assets/icons/pencil.svg new file mode 100644 index 0000000..f353f82 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/pencil.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/play.svg b/apps/desktop/src/renderer/src/design/assets/icons/play.svg new file mode 100644 index 0000000..097b7b8 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/play.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/plus.svg b/apps/desktop/src/renderer/src/design/assets/icons/plus.svg new file mode 100644 index 0000000..e1280bc --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/plus.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/refresh-cw.svg b/apps/desktop/src/renderer/src/design/assets/icons/refresh-cw.svg new file mode 100644 index 0000000..7c321ae --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/refresh-cw.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/search.svg b/apps/desktop/src/renderer/src/design/assets/icons/search.svg new file mode 100644 index 0000000..49ff049 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/search.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/send.svg b/apps/desktop/src/renderer/src/design/assets/icons/send.svg new file mode 100644 index 0000000..95c2c70 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/send.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/settings-2.svg b/apps/desktop/src/renderer/src/design/assets/icons/settings-2.svg new file mode 100644 index 0000000..634bb98 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/settings-2.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/settings.svg b/apps/desktop/src/renderer/src/design/assets/icons/settings.svg new file mode 100644 index 0000000..1bd15d5 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/settings.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/sparkles.svg b/apps/desktop/src/renderer/src/design/assets/icons/sparkles.svg new file mode 100644 index 0000000..ca74b84 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/sparkles.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/square-kanban.svg b/apps/desktop/src/renderer/src/design/assets/icons/square-kanban.svg new file mode 100644 index 0000000..bf1a8e3 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/square-kanban.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/sun.svg b/apps/desktop/src/renderer/src/design/assets/icons/sun.svg new file mode 100644 index 0000000..83fb8bb --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/sun.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/tag.svg b/apps/desktop/src/renderer/src/design/assets/icons/tag.svg new file mode 100644 index 0000000..53b2bb4 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/tag.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/target.svg b/apps/desktop/src/renderer/src/design/assets/icons/target.svg new file mode 100644 index 0000000..3061a89 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/target.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/trash-2.svg b/apps/desktop/src/renderer/src/design/assets/icons/trash-2.svg new file mode 100644 index 0000000..4bbb166 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/trash-2.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/trending-up.svg b/apps/desktop/src/renderer/src/design/assets/icons/trending-up.svg new file mode 100644 index 0000000..0bca7ef --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/trending-up.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/triangle-alert.svg b/apps/desktop/src/renderer/src/design/assets/icons/triangle-alert.svg new file mode 100644 index 0000000..4601160 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/triangle-alert.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/user.svg b/apps/desktop/src/renderer/src/design/assets/icons/user.svg new file mode 100644 index 0000000..bc66de3 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/user.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/users.svg b/apps/desktop/src/renderer/src/design/assets/icons/users.svg new file mode 100644 index 0000000..6a708fe --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/users.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/x.svg b/apps/desktop/src/renderer/src/design/assets/icons/x.svg new file mode 100644 index 0000000..4eb59f3 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/x.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/icons/zap.svg b/apps/desktop/src/renderer/src/design/assets/icons/zap.svg new file mode 100644 index 0000000..f750eb0 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/assets/icons/zap.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/src/design/assets/logo-icon.png b/apps/desktop/src/renderer/src/design/assets/logo-icon.png new file mode 100644 index 0000000..8f91eee Binary files /dev/null and b/apps/desktop/src/renderer/src/design/assets/logo-icon.png differ diff --git a/apps/desktop/src/renderer/src/design/assets/logo.jpeg b/apps/desktop/src/renderer/src/design/assets/logo.jpeg new file mode 100644 index 0000000..951dd2a Binary files /dev/null and b/apps/desktop/src/renderer/src/design/assets/logo.jpeg differ diff --git a/apps/desktop/src/renderer/src/design/styles.css b/apps/desktop/src/renderer/src/design/styles.css new file mode 100644 index 0000000..bb57c82 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/styles.css @@ -0,0 +1,5 @@ +@import "tokens/fonts.css"; +@import "tokens/colors.css"; +@import "tokens/typography.css"; +@import "tokens/spacing.css"; +@import "tokens/base.css"; diff --git a/apps/desktop/src/renderer/src/design/tokens/base.css b/apps/desktop/src/renderer/src/design/tokens/base.css new file mode 100644 index 0000000..e491680 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/tokens/base.css @@ -0,0 +1,37 @@ +/* CommiTea base element styles. */ + +* { box-sizing: border-box; } + +body { + margin: 0; + font: var(--text-body); + color: var(--text-body); + background: var(--surface-app); + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +a { + color: var(--accent-text); + text-decoration: none; + border-bottom: 1px solid var(--line-2); + transition: border-color var(--duration-fast) var(--ease-out); +} +a:hover { + color: var(--accent-hover); + border-bottom-color: var(--jade); +} + +::selection { + background: var(--spruce-2); + color: var(--ink-1); +} + +code, kbd { + font: var(--text-data); +} + +:focus-visible { + outline: 2px solid var(--focus-ring); + outline-offset: 2px; +} diff --git a/apps/desktop/src/renderer/src/design/tokens/colors.css b/apps/desktop/src/renderer/src/design/tokens/colors.css new file mode 100644 index 0000000..1d237cf --- /dev/null +++ b/apps/desktop/src/renderer/src/design/tokens/colors.css @@ -0,0 +1,137 @@ +/* CommiTea color system — "Green tea service" + Cool porcelain surfaces, slate-green ink, jade accent (from the cup itself). + Light is default (morning service). Dark scope: [data-theme="dark"] (evening service). */ + +:root { + /* ---- base: porcelain (cool green-white surfaces) ---- */ + --paper-0: #F0F4F0; /* app background */ + --paper-1: #FAFCF9; /* raised card */ + --paper-2: #E4EBE4; /* inset wells, rails */ + --paper-3: #D7E1D8; /* pressed / deep inset */ + + /* ---- base: ink (slate-green text scale) ---- */ + --ink-1: #1D2522; /* primary text */ + --ink-2: #51605A; /* secondary text */ + --ink-3: #84928B; /* muted / placeholder */ + --ink-inverse: #EFF5F0; + + /* ---- base: lines ---- */ + --line-1: #D7DFD7; /* hairline */ + --line-2: #BACBBE; /* strong rule */ + + /* ---- base: spruce (brand green — the logo's slate-teal) ---- */ + --spruce-9: #16322A; + --spruce-8: #1D4238; + --spruce-7: #275546; /* brand anchor */ + --spruce-6: #3A7260; + --spruce-5: #57937C; + --spruce-3: #A3CFBB; + --spruce-2: #CDE5D7; + --spruce-1: #E3F0E8; + + /* ---- base: jade (mint accent — the tea itself) ---- */ + --jade-7: #2E7A57; + --jade-6: #46996F; + --jade-5: #66B389; + --jade-3: #A8DDBE; + --jade-1: #E0F4E7; + + /* ---- semantic: surfaces & text ---- */ + --surface-app: var(--paper-0); + --surface-card: var(--paper-1); + --surface-inset: var(--paper-2); + --surface-raised: var(--paper-1); + --text-body: var(--ink-1); + --text-secondary: var(--ink-2); + --text-muted: var(--ink-3); + --text-on-accent: var(--ink-inverse); + --border-hairline: var(--line-1); + --border-strong: var(--line-2); + + /* ---- semantic: interactive ---- */ + --accent: var(--spruce-7); + --accent-hover: #1F4A3C; + --accent-pressed: #173B30; + --accent-tint: var(--spruce-1); + --accent-text: var(--spruce-7); + --jade: var(--jade-6); + --jade-tint: var(--jade-1); + --focus-ring: #57937C; + + /* ---- semantic: status ---- */ + --ok: #2F7D53; + --ok-tint: #DFEEE3; + --warn: #96772A; + --warn-tint: #EFE9D2; + --danger: #A84632; + --danger-tint: #F2E0DA; + --info: #43758F; + --info-tint: #DFE9EC; + + /* ---- domain: label chips (gitea label sets) ---- */ + --label-est-bg: var(--paper-2); + --label-est-text: var(--ink-2); + --label-p1: #A84632; + --label-p2: #96772A; + --label-p3: #43758F; + --label-p4: #84928B; + --label-hard: #A84632; + + /* ---- domain: forecast ---- */ + --cone-fill: rgba(102, 179, 137, 0.18); + --cone-line: #46996F; + --cone-actual: var(--ink-1); +} + +[data-theme="dark"] { + /* evening service */ + --paper-0: #121C18; + --paper-1: #182420; + --paper-2: #1F2E28; + --paper-3: #283A32; + + --ink-1: #E4EEE7; + --ink-2: #9FB2A8; + --ink-3: #66796F; + --ink-inverse: #EFF5F0; + + --line-1: #263630; + --line-2: #35493F; + + --spruce-1: #1E332B; + --spruce-2: #27443A; + --spruce-3: #3A6353; + + --jade-1: #1F3A2E; + --jade-3: #35624A; + + --accent: #3A7260; + --accent-hover: #448069; + --accent-pressed: #315F51; + --accent-tint: #1E332B; + --accent-text: #8CC7AC; + --jade: #6FC694; + --jade-tint: #1F3A2E; + --focus-ring: #57937C; + + --ok: #74B992; + --ok-tint: #1F3529; + --warn: #C0A45B; + --warn-tint: #33301F; + --danger: #C77B62; + --danger-tint: #392823; + --info: #7FA6BC; + --info-tint: #223038; + + --label-est-bg: var(--paper-3); + --label-est-text: var(--ink-2); + --label-p1: #C77B62; + --label-p2: #C0A45B; + --label-p3: #7FA6BC; + --label-p4: #66796F; + --label-hard: #C77B62; + + --cone-fill: rgba(111, 198, 148, 0.14); + --cone-line: #6FC694; + --cone-actual: #E4EEE7; +} diff --git a/apps/desktop/src/renderer/src/design/tokens/fonts.css b/apps/desktop/src/renderer/src/design/tokens/fonts.css new file mode 100644 index 0000000..56074d5 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/tokens/fonts.css @@ -0,0 +1,65 @@ +/* CommiTea webfonts — self-hosted, latin subsets */ + +@font-face { + font-family: 'Libre Caslon Display'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../assets/fonts/caslon-display-normal-400.woff2) format('woff2'); +} +@font-face { + font-family: 'Libre Caslon Text'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../assets/fonts/caslon-text-normal-400.woff2) format('woff2'); +} +@font-face { + font-family: 'Libre Caslon Text'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(../assets/fonts/caslon-text-italic-400.woff2) format('woff2'); +} +@font-face { + font-family: 'Libre Caslon Text'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(../assets/fonts/caslon-text-normal-700.woff2) format('woff2'); +} +@font-face { + font-family: 'Instrument Sans'; + font-style: normal; + font-weight: 400 700; + font-display: swap; + src: url(../assets/fonts/instrument-sans-normal-400-700.woff2) format('woff2'); +} +@font-face { + font-family: 'Instrument Sans'; + font-style: italic; + font-weight: 400 700; + font-display: swap; + src: url(../assets/fonts/instrument-sans-italic-400-700.woff2) format('woff2'); +} +@font-face { + font-family: 'IBM Plex Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../assets/fonts/plex-mono-normal-400.woff2) format('woff2'); +} +@font-face { + font-family: 'IBM Plex Mono'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(../assets/fonts/plex-mono-normal-500.woff2) format('woff2'); +} +@font-face { + font-family: 'IBM Plex Mono'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(../assets/fonts/plex-mono-normal-600.woff2) format('woff2'); +} diff --git a/apps/desktop/src/renderer/src/design/tokens/spacing.css b/apps/desktop/src/renderer/src/design/tokens/spacing.css new file mode 100644 index 0000000..0bc88ca --- /dev/null +++ b/apps/desktop/src/renderer/src/design/tokens/spacing.css @@ -0,0 +1,44 @@ +/* CommiTea spacing, radius, shadow, border & motion tokens. */ + +:root { + /* spacing — 4px base */ + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + --space-7: 32px; + --space-8: 40px; + --space-9: 48px; + --space-10: 64px; + + /* radii — crisp, not bubbly */ + --radius-1: 4px; /* chips, inputs' inner elements */ + --radius-2: 6px; /* buttons, inputs */ + --radius-3: 10px; /* cards, dialogs */ + --radius-round: 999px; + + /* borders */ + --border-w: 1px; + --rule-double: 3px double var(--border-strong); /* fine-stationery section rule */ + + /* shadows — cool, restrained */ + --shadow-1: 0 1px 2px rgba(20, 34, 28, 0.06); + --shadow-2: 0 2px 8px rgba(20, 34, 28, 0.08), 0 1px 2px rgba(20, 34, 28, 0.05); + --shadow-3: 0 12px 32px rgba(20, 34, 28, 0.14), 0 2px 8px rgba(20, 34, 28, 0.07); + --shadow-jade-line: inset 0 2px 0 var(--jade); /* jade top rule on key cards */ + + /* motion — settled, never bouncy */ + --ease-out: cubic-bezier(0.25, 0.6, 0.3, 1); /* @kind other */ + --ease-in-out: cubic-bezier(0.6, 0, 0.3, 1); /* @kind other */ + --duration-fast: 120ms; /* @kind other */ + --duration-base: 200ms; /* @kind other */ + --duration-slow: 320ms; /* @kind other */ +} + +[data-theme="dark"] { + --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.25); + --shadow-2: 0 2px 8px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2); + --shadow-3: 0 12px 32px rgba(0, 0, 0, 0.45), 0 2px 8px rgba(0, 0, 0, 0.25); +} diff --git a/apps/desktop/src/renderer/src/design/tokens/typography.css b/apps/desktop/src/renderer/src/design/tokens/typography.css new file mode 100644 index 0000000..2556118 --- /dev/null +++ b/apps/desktop/src/renderer/src/design/tokens/typography.css @@ -0,0 +1,34 @@ +/* CommiTea typography tokens. + Voices: Display serif (headlines, big numerals) · Reginald's voice (Caslon Text, upright) + · Body sans (UI) · Mono (data: labels, estimates, dates, ids). */ + +:root { + --font-serif-display: 'Libre Caslon Display', 'Libre Caslon Text', Georgia, serif; + --font-serif-text: 'Libre Caslon Text', Georgia, serif; + --font-sans: 'Instrument Sans', -apple-system, 'Segoe UI', sans-serif; + --font-mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', monospace; + + /* type scale */ + --text-hero: 400 48px/1.08 var(--font-serif-display); /* big forecast numerals */ + --text-display: 400 34px/1.15 var(--font-serif-display); /* page titles */ + --text-title: 400 24px/1.2 var(--font-serif-display); /* card titles */ + --text-agent: 400 16px/1.55 var(--font-serif-text); /* Reginald speaks — serif, upright */ + --text-agent-lg: 400 20px/1.5 var(--font-serif-text); + --text-body: 400 14px/1.5 var(--font-sans); + --text-body-strong: 600 14px/1.5 var(--font-sans); + --text-small: 400 13px/1.45 var(--font-sans); + --text-caption: 400 12px/1.4 var(--font-sans); + --text-data: 400 13px/1.4 var(--font-mono); /* dates, counts */ + --text-label: 500 11.5px/1 var(--font-mono); /* chips: est/2d, p/1 */ + --text-overline: 600 11px/1.2 var(--font-sans); /* + letter-spacing-wide */ + + /* tracking */ + --letter-spacing-wide: 0.08em; /* overlines, uppercase */ + --letter-spacing-label: 0.02em; /* mono chips */ + + /* weights (sans is variable 400–700) */ + --weight-regular: 400; + --weight-medium: 500; + --weight-semibold: 600; + --weight-bold: 700; +} diff --git a/apps/desktop/src/renderer/src/index.css b/apps/desktop/src/renderer/src/index.css new file mode 100644 index 0000000..fc163a2 --- /dev/null +++ b/apps/desktop/src/renderer/src/index.css @@ -0,0 +1,5 @@ +@import './design/styles.css'; + +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/apps/desktop/src/renderer/src/main.tsx b/apps/desktop/src/renderer/src/main.tsx new file mode 100644 index 0000000..f82e182 --- /dev/null +++ b/apps/desktop/src/renderer/src/main.tsx @@ -0,0 +1,11 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' + +import { App } from './app.js' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/apps/desktop/tailwind.config.js b/apps/desktop/tailwind.config.js new file mode 100644 index 0000000..2c91b4b --- /dev/null +++ b/apps/desktop/tailwind.config.js @@ -0,0 +1,67 @@ +/** + * Tailwind maps onto the design-token custom properties (src/renderer/src/design/tokens/) + * and never redefines their values. Preflight is off — tokens/base.css owns the reset. + * @type {import('tailwindcss').Config} + */ +export default { + content: ['./src/renderer/index.html', './src/renderer/src/**/*.{ts,tsx}'], + corePlugins: { preflight: false }, + theme: { + extend: { + colors: { + paper: { + 0: 'var(--paper-0)', + 1: 'var(--paper-1)', + 2: 'var(--paper-2)', + 3: 'var(--paper-3)', + }, + ink: { + 1: 'var(--ink-1)', + 2: 'var(--ink-2)', + 3: 'var(--ink-3)', + inverse: 'var(--ink-inverse)', + }, + line: { + 1: 'var(--line-1)', + 2: 'var(--line-2)', + }, + accent: { + DEFAULT: 'var(--accent)', + hover: 'var(--accent-hover)', + pressed: 'var(--accent-pressed)', + tint: 'var(--accent-tint)', + text: 'var(--accent-text)', + }, + jade: { + DEFAULT: 'var(--jade)', + tint: 'var(--jade-tint)', + }, + ok: { DEFAULT: 'var(--ok)', tint: 'var(--ok-tint)' }, + warn: { DEFAULT: 'var(--warn)', tint: 'var(--warn-tint)' }, + danger: { DEFAULT: 'var(--danger)', tint: 'var(--danger-tint)' }, + info: { DEFAULT: 'var(--info)', tint: 'var(--info-tint)' }, + }, + fontFamily: { + display: ['Libre Caslon Display', 'serif'], + agent: ['Libre Caslon Text', 'serif'], + sans: ['Instrument Sans', 'system-ui', 'sans-serif'], + mono: ['IBM Plex Mono', 'ui-monospace', 'monospace'], + }, + borderRadius: { + 1: 'var(--radius-1)', + 2: 'var(--radius-2)', + 3: 'var(--radius-3)', + }, + boxShadow: { + 1: 'var(--shadow-1)', + 2: 'var(--shadow-2)', + 3: 'var(--shadow-3)', + 'jade-line': 'var(--shadow-jade-line)', + }, + transitionTimingFunction: { + out: 'var(--ease-out)', + }, + }, + }, + plugins: [], +} diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json new file mode 100644 index 0000000..fa4dd57 --- /dev/null +++ b/apps/desktop/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["node"] + }, + "include": ["src", "electron.vite.config.ts"] +} diff --git a/docs/PLAN.md b/docs/PLAN.md new file mode 100644 index 0000000..63d92cb --- /dev/null +++ b/docs/PLAN.md @@ -0,0 +1,124 @@ +# CommiTea — AI-PM on Gitea + +Plan file. Created 2026-07-07 from brainstorm session. Status: **pre-design** — +design session next, phases below get actuals as they complete. + +## Concept + +Lightweight project tracking/management system. Gitea = core engine for +milestones/tickets. Local-ish LLM agent acts as project manager: captures work +via interview, maps capacity, forecasts deadlines, adjusts priorities on +directives. Electron app, one window, "tell me what to do" experience. + +## Goals (numeric where possible) + +1. **Forecast honesty**: milestone forecasts as Monte Carlo cones (e.g. "80% + land Mar 3–12"), never point dates. Calibration curve active after ≥20 + closed issues with estimates. +2. **Capture speed**: braindump → approved ticket set (estimates, deps, + milestone) in < 2 min via agent interview. +3. **Responsiveness**: full gitea reconcile < 5 s @ 500 issues; scheduler + + Monte Carlo run < 1 s @ 200 open issues; webhook-driven updates visible + < 2 s. +4. **Agent economy**: hot context ≤ 2k tokens; works with a 4B local model for + prose/ritual tasks (bigger model only for decomposition/negotiation). +5. **Zero pollution**: managed work repos gain only human-meaningful labels + (`est/*`, `p/*`, `deadline/hard`) — no bot comments, no body frontmatter, + no synthetic issues. + +## Core architecture decisions (settled in brainstorm) + +- **Purity rule**: gitea holds *human-authored intent* (issues, milestones + + due dates, dependencies, assignees, labels, comments). Sidecar holds + *machine-derived state* (forecasts, calibration, inferred lifecycle + timestamps, capacity, agent memory). Test: delete sidecar → resync → no + truth lost. +- **`pm-state` repo in gitea** = shared sidecar store. Versioned files; + directive log is append-only JSONL (merges conflict-free). Local SQLite is + a rebuildable cache/index only. +- **Estimates/priority visible in gitea** as fixed label sets: `est/1d 2d 3d + 5d 8d`, `p/1..4`; `deadline/hard` label on milestones (agent asks hard/soft + at creation). Estimate unit = days. +- **LLM never does math.** Deterministic scheduler (code) computes forecasts, + critical path, next-unit-of-work from estimates + deps + capacity + + priority. Evidence-based scheduling: Monte Carlo over the team's own + estimate-vs-actual history. LLM captures/negotiates inputs and explains + outputs. +- **Lifecycle inference, no manual time tracking**: issue opened → diagnosis; + labeled/milestoned → triage; first branch/commit ref → work start; PR + merged → deploy; closed → complete. Timestamps from gitea events feed + actuals. +- **Directives are first-class**: append-only log entry (who/when/what/why) → + scheduler re-run → agent presents consequence diff ("X today, milestone Y + +6d — accept?"). Directive-giver is a role; v1 = one PM (Stephen). +- **Model layer**: OpenAI-wire-protocol client, base-URL + model per role. + Router: small local (gemma-4b class) for summaries/standup prose; big model + (remote LM Studio box or OpenAI API) for decomposition/estimate + negotiation. Few, fat tools (e.g. one `query_project`) so small models + survive. +- **Agent conduct**: interview > dumping for capture; propose-approve for + destructive ops, direct-act for additive; morning-standup ritual (drift + report, today's plan, stale-blocker nagging). +- **Memory**: hot (charter + active directives + focus snapshot, ≤2k tok) / + warm (append-only event log, weekly digests) / cold (gitea + sidecar via + tools — ticket data never copied into memory). No graph in v1. +- **Sync**: per-instance gitea webhooks while running + full reconcile on + launch + light poll fallback. +- **Multi-contributor v1**: gitea users as identities; per-person capacity = + hours/day × focus factor + standing allocation slices (dev/compliance/ + pilots). Multi-*writer* (concurrent directive-givers) deferred. +- **Repos**: CommiTea source lives in its own repo (`commitea`) on + gitea.stephenmann.io; targets N work repos via config (v1: one). Dogfood: + CommiTea's own backlog is the first managed project. +- **Stack**: Electron + React + Tailwind (novelpad patterns), one window. + +## UI (to be designed — design session) + +- Hero: **Now/Next/Later focus card** — scheduler-picked next unit, agent's + one-line rationale. +- **Burn-up with forecast cone** (ahead/behind as geometry), **runway view** + (capacity vs milestone dates). +- Secondary drill-ins: dependency graph / Gantt, kanban. +- Chat = write-path (mutations via agent), UI = read-path. Exact split to be + settled in design session. + +## Phases + +Deliverables per phase; replace TBD with actuals at phase close. + +- **P0 — Scaffold** — repo, Electron shell, gitea API client + token auth, + `pm-state` repo bootstrap. Actual: TBD +- **P1 — Sync + data model** — read mirror into SQLite cache, webhook + listener + reconcile-on-launch, lifecycle inference from event stream, + label schema applied. Actual: TBD +- **P2 — Scheduler + Monte Carlo** — deterministic forecast engine, capacity + model, calibration store (cold-start: default distributions until n≥20), + directive log + consequence diff. Actual: TBD +- **P3 — UI views** — focus card, burn-up cone, runway, drill-ins. Actual: TBD +- **P4 — Agent** — model router, fat tools, capture interview, propose- + approve loop, standup ritual, memory layers. Actual: TBD +- **P5 — Dogfood + polish** — manage CommiTea with CommiTea; calibrate. + Actual: TBD + +(Order note: P3 before P4 so the agent has something to point at; thin +vertical slices within phases where possible.) + +## Not doing (v1) + +- Google Calendar capacity import (phase 2: free/busy read-only per member) +- Phone/push notifications +- Agent code review / PR content reading (PR *events* are in for lifecycle) +- Graph-structured agent memory +- PTO/meeting calendars +- Concurrent multi-PM directive writing +- Cloud LLM as *requirement* (it's a config option, not a dependency) + +## Open items for design session + +- Chat-as-only-write-path: strict or soft (UI edits allowed but agent + observes/objects)? +- `pm-state` file formats (JSONL event log settled; capacity/calibration + schemas TBD) +- Tool schema for the fat `query_project` tool +- Webhook endpoint mechanics per instance (port allocation, cleanup on quit) +- Cold-start estimate distributions (industry priors vs uniform pessimism) diff --git a/docs/design/README.md b/docs/design/README.md new file mode 100644 index 0000000..57e5eea --- /dev/null +++ b/docs/design/README.md @@ -0,0 +1,74 @@ +# Handoff: CommiTea App + +## Overview +CommiTea is an AI-project-manager built on Gitea: an Electron desktop app (one window) where an LLM agent persona ("Reginald") captures work via interview, forecasts milestones as Monte Carlo ranges, and manages issues through labels only. This package contains the complete design system plus a fully interactive HTML prototype of the app with 11 screens. + +## About the Design Files +**The files in this bundle are design references created in HTML** — prototypes showing intended look and behavior, not production code to copy directly. Your task is to **recreate these designs in the target codebase's environment**. The product plan specifies **Electron + React + Tailwind**; if that scaffold doesn't exist yet, that is the intended stack. Map the CSS custom properties in `tokens/` to Tailwind theme config; reimplement the `components/` primitives as your base component library. + +`ui_kits/app/index.html` opens directly in a browser (via a local static server) — use it as the living spec. Everything is real: navigation, drill-ins, theme toggle, offline simulation. + +## Fidelity +**High-fidelity.** Colors, typography, spacing, radii, shadows, copy, and interactions are final. Recreate pixel-perfectly using the token values (all defined in `tokens/*.css`). The fixture data in `ui_kits/app/data.js` is demo content — replace with real Gitea/scheduler data. + +## Design System (source of truth) +- `design_system_readme.md` — brand context, CONTENT FUNDAMENTALS (Reginald's voice rules: dry wit, no emoji, sentence case, forecasts always ranges), VISUAL FOUNDATIONS, ICONOGRAPHY. +- `styles.css` → imports `tokens/fonts.css` (self-hosted woff2 in `assets/fonts/`), `tokens/colors.css` (light `:root` + dark `[data-theme="dark"]`), `tokens/typography.css`, `tokens/spacing.css`, `tokens/base.css`. +- `components/{core,forms,feedback}/` — 16 React primitives (Button, IconButton, Badge, Tag, Card, Tabs, Icon, Input, Select, Checkbox, Radio, Switch, Dialog, Toast, Tooltip). Each has `.jsx` (reference implementation), `.d.ts` (props contract), `.prompt.md` (usage + rules). Reuse these contracts verbatim. +- `_ds_bundle.js` — compiled bundle the prototype loads; not for production. + +### Type voices (strictly cast — never mix) +- `--text-display/title/hero`: Libre Caslon Display — headlines, hero numerals +- `--text-agent(-lg)`: Libre Caslon Text, **upright** — ONLY for Reginald's speech +- `--text-body/small/caption`: Instrument Sans — all UI +- `--text-data/label`: IBM Plex Mono — machine data verbatim: `est/3d`, `p/1`, `#87`, dates, deltas + +### Key color tokens (light / dark) +- Surfaces: `--paper-0` #F0F4F0/#121C18 (app), `--paper-1` #FAFCF9/#182420 (card), `--paper-2` inset, `--paper-3` pressed +- Ink: `--ink-1` #1D2522/#E4EEE7, `--ink-2`, `--ink-3`; hairlines `--line-1/2` +- Brand: `--accent` (spruce #275546/#3A7260), hover/pressed darker steps; `--jade` #46996F/#6FC694 (small accents + key-card top rule `--shadow-jade-line` only — never large fills) +- Status: `--ok --warn --danger --info` + `*-tint` pairs; label chips `--label-p1..p4`, `--label-est-*`, `--label-hard` +- Forecast: `--cone-fill`, `--cone-line`, `--cone-actual` +- Radii 4/6/10px; spacing 4px scale (`--space-1..10`); motion 120–320ms `--ease-out`, no bounces; focus = 2px `--focus-ring` outline offset 2. + +## Screens (all in `ui_kits/app/`, routed by `Shell.jsx`) +Layout shell: 208px left rail (nav + connection status + theme switch) · main column (max 1120px, 24/28px padding) · 330px right chat panel. Page headers: Caslon display title over a `3px double` rule, mono subtitle. One `jade` (top-ruled) card per view maximum. + +1. **Shell** (`Shell.jsx`) — nav, routing, dark mode (`data-theme` on ``), offline simulation (click connection dot), issue navigation with back-stack of one. +2. **Standup** (`StandupScreen.jsx`) — morning ritual as a typeset letter: drift report (dot + text + mono delta), per-person plan, stale-blocker nag (warn tint, clickable). Sections fade in once, 90ms stagger, reduced-motion-safe. +3. **Morning service / Focus** (`FocusScreen.jsx`) — Now/Next/Later cards (Now = jade + footer actions), burn-up cone card (`Chart.jsx` BurnUpCone SVG). +4. **Inbox** (`InboxScreen.jsx`) — day-grouped notifications, filter Tabs, unread dots, live rail badge; rows navigate to issue/directives; "Mark all read". +5. **Capture** (`CaptureScreen.jsx`) — braindump → chip-answered interview (running mm:ss clock, amber past 2:00) → editable tray review with consequence line → filed. State machine: dump | interview | review | filed. +6. **The pot / Board** (`BoardScreen.jsx`) — 5-column lifecycle kanban (Diagnosis/Triage/Steeping/In review/Done), live search with empty state, Tabs to drill-ins. +7. **Gantt** (`GanttView.jsx`) — scheduler bars by state, critical rows inset-ruled, dotted 80% tails, today rule (jade), milestone band + due diamond. +8. **Dependencies** (`DepsGraph.jsx`) — layered DAG, SVG bezier edges w/ arrowheads, critical path in accent, milestone capsule terminal. +9. **Runway** (`RunwayScreen.jsx`) — milestone range bars (due marker vs 80% band), capacity list, calibration teaser → **Calibration** (`CalibrationScreen.jsx`): scatter + honest diagonal + ×1.18 fit, bias-by-label bars, per-person bias, forecast effect. Milestone rows → **Milestone detail** (`MilestoneScreen.jsx`): stats strip, cone, grouped issue list. +10. **Issue detail** (`IssueScreen.jsx`) — human intent left (description, comments, composer "writes to gitea, as you"), machine-derived right (inferred lifecycle timeline, per-issue forecast, dependency chips, provenance note). +11. **Directives** (`DirectivesScreen.jsx`) — pending consequence diff (before → after mono rows; Make it so / Amend / Withdraw) + append-only ledger (seq, who/when/why, verbatim quote, status badge). +12. **Settings** (`SettingsScreen.jsx`) — connection, sync switches, model router, read-only label schema, rituals, appearance radios, single danger action. +13. **Onboarding** (`OnboardingScreen.jsx`, "First run") — full-window: welcome → connect (test gate) → repo pick → propose-approve bootstrap. +14. **States** (`StatesGallery.jsx`) — EmptyState/OfflineBanner/ModelAwayState components + gallery of all empty & trouble states. + +## Interactions & Behavior +- Hovers: surfaces step one paper darker; buttons darken (`--accent-hover`); never opacity on text. Press: one step darker again, no shrink. +- All fake async (connection test ~1.1s, chat reply ~0.9s, bootstrap ticks 700ms) represent real network calls. +- Offline mode: amber banner (copy in `StatesGallery.jsx`), chat composer disabled + queue note, red rail dot. Reads work from cache; writes queue. +- Issue click anywhere → issue page; Back returns to origin view. +- Chat = the only write path for mutations; destructive ops always propose-approve (Dialog or consequence diff). + +## State Management (suggested) +- `view` routing + `prevView` (back), `dark`, `offline`, `issue` selection, inbox `readIds`, capture state machine, directives pending/ledger. In production: Gitea REST + webhooks fill a local cache (SQLite per plan); scheduler output feeds focus/gantt/runway; all copy rules live in the design-system readme. + +## Assets +- `assets/fonts/` — Libre Caslon Display/Text, Instrument Sans, IBM Plex Mono (woff2, latin, self-hosted; Google Fonts sources) +- `assets/icons/` — 69 Lucide SVGs (lucide-static v0.462.0, ISC). In production use the `lucide-react` package at strokeWidth 1.5. +- `assets/logo.jpeg` (original), `assets/logo-icon.png` (cropped, rounded) — provided brand mark; never redraw. + +## Files +- `ui_kits/app/index.html` — entry; open with a static server from this folder's root +- `ui_kits/app/*.jsx` — one file per screen (see Screens above); `data.js` — all fixture data +- `styles.css`, `tokens/`, `components/`, `assets/` — the design system +- `SKILL.md` — agent-facing skill entry (works with Claude Code as an Agent Skill) + +## Note on file suffixes +Reference source files in this bundle carry a `.txt` suffix (`*.js.txt`, `*.d-ts.txt`) so the design tool's compiler ignores these copies — the originals live in the design project. They are ordinary JSX/TypeScript inside; strip the suffix after copying into your repo. `ui_kits/app/index.html` still runs as-is (Babel loads the .txt files by src). diff --git a/docs/design/SKILL.md b/docs/design/SKILL.md new file mode 100644 index 0000000..a78bd6c --- /dev/null +++ b/docs/design/SKILL.md @@ -0,0 +1,16 @@ +--- +name: commitea-design +description: Use this skill to generate well-branded interfaces and assets for CommiTea, either for production or throwaway prototypes/mocks/etc. Contains essential design guidelines, colors, type, fonts, assets, and UI kit components for prototyping. +user-invocable: true +--- + +Read the README.md file within this skill, and explore the other available files. +If creating visual artifacts (slides, mocks, throwaway prototypes, etc), copy assets out and create static HTML files for the user to view. If working on production code, you can copy assets and read the rules here to become an expert in designing with this brand. +If the user invokes this skill without any other guidance, ask them what they want to build or design, ask some questions, and act as an expert designer who outputs HTML artifacts _or_ production code, depending on the need. + +Key facts: +- Brand: CommiTea — AI project manager on Gitea. The agent persona is **Reginald**: brilliant English gentleman PM, dry wit, numbers sacred. No emoji, sentence case, forecasts are ranges never point dates. Reginald's speech renders in upright Caslon serif; never italic, never "the AI". +- Global CSS entry: `styles.css` (imports `tokens/*.css`). Fonts self-hosted in `assets/fonts/`; icons are local Lucide SVGs in `assets/icons/`. +- Type voices, strictly cast: Libre Caslon Display (headlines/hero numerals), Libre Caslon Text upright (Reginald's speech only — the agent persona), Instrument Sans (UI), IBM Plex Mono (machine data: labels, dates, ids). +- Components live in `components/{core,forms,feedback}/` — each has a `.prompt.md` with usage. +- Full app reference: `ui_kits/app/index.html`. diff --git a/docs/design/_ds_bundle.js b/docs/design/_ds_bundle.js new file mode 100644 index 0000000..e277e10 --- /dev/null +++ b/docs/design/_ds_bundle.js @@ -0,0 +1,6692 @@ +/* @ds-bundle: {"format":4,"namespace":"CommiTeaDesignSystem_20e63b","components":[{"name":"Badge","sourcePath":"components/core/Badge.jsx"},{"name":"Button","sourcePath":"components/core/Button.jsx"},{"name":"Card","sourcePath":"components/core/Card.jsx"},{"name":"Icon","sourcePath":"components/core/Icon.jsx"},{"name":"ICON_NAMES","sourcePath":"components/core/Icon.jsx"},{"name":"IconButton","sourcePath":"components/core/IconButton.jsx"},{"name":"Tabs","sourcePath":"components/core/Tabs.jsx"},{"name":"Tag","sourcePath":"components/core/Tag.jsx"},{"name":"Dialog","sourcePath":"components/feedback/Dialog.jsx"},{"name":"Toast","sourcePath":"components/feedback/Toast.jsx"},{"name":"Tooltip","sourcePath":"components/feedback/Tooltip.jsx"},{"name":"Checkbox","sourcePath":"components/forms/Checkbox.jsx"},{"name":"Input","sourcePath":"components/forms/Input.jsx"},{"name":"Radio","sourcePath":"components/forms/Radio.jsx"},{"name":"Select","sourcePath":"components/forms/Select.jsx"},{"name":"Switch","sourcePath":"components/forms/Switch.jsx"}],"sourceHashes":{"components/core/Badge.jsx":"13c23a483d6d","components/core/Button.jsx":"ffc4f2a60c23","components/core/Card.jsx":"e4f4f0a376af","components/core/Icon.jsx":"1787a3656dc6","components/core/IconButton.jsx":"170d08e66f39","components/core/Tabs.jsx":"0f8158ee544f","components/core/Tag.jsx":"302fa43de0bd","components/feedback/Dialog.jsx":"73351848bd2e","components/feedback/Toast.jsx":"f9a033ad288d","components/feedback/Tooltip.jsx":"56a0f8711a27","components/forms/Checkbox.jsx":"4cb2969560f3","components/forms/Input.jsx":"094796149aa2","components/forms/Radio.jsx":"91f9c6b050f9","components/forms/Select.jsx":"9ff161748dde","components/forms/Switch.jsx":"f06333a8ce87","ui_kits/app/BoardScreen.jsx":"dbc1bd0a53b7","ui_kits/app/CalibrationScreen.jsx":"c91b96dc9bf5","ui_kits/app/CaptureScreen.jsx":"73340a249734","ui_kits/app/Chart.jsx":"15c1e3215058","ui_kits/app/ChatPanel.jsx":"339468410bb2","ui_kits/app/DepsGraph.jsx":"939049a86dda","ui_kits/app/DirectivesScreen.jsx":"e2b00e10749b","ui_kits/app/FocusScreen.jsx":"299cda1464ad","ui_kits/app/GanttView.jsx":"1f80d0b66429","ui_kits/app/InboxScreen.jsx":"e6cb39fc8ffe","ui_kits/app/IssueScreen.jsx":"4a796332482d","ui_kits/app/MilestoneScreen.jsx":"c2079e4d6380","ui_kits/app/OnboardingScreen.jsx":"640268f5031f","ui_kits/app/RunwayScreen.jsx":"038c4bfad5fe","ui_kits/app/SettingsScreen.jsx":"310bf0c6283b","ui_kits/app/Shell.jsx":"9135061e542a","ui_kits/app/StandupScreen.jsx":"70abb71f7c43","ui_kits/app/StatesGallery.jsx":"324b05e164e9","ui_kits/app/data.js":"df66d90a9521"},"inlinedExternals":[],"unexposedExports":[]} */ + +(() => { + +const __ds_ns = (window.CommiTeaDesignSystem_20e63b = window.CommiTeaDesignSystem_20e63b || {}); + +const __ds_scope = {}; + +(__ds_ns.__errors = __ds_ns.__errors || []); + +// components/core/Badge.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const CSS = ` +.ct-badge { + display: inline-flex; align-items: center; gap: 6px; + font: 500 12px/1 var(--font-sans); + padding: 4px 9px; + border-radius: var(--radius-round); +} +.ct-badge__dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; } +.ct-badge--ok { background: var(--ok-tint); color: var(--ok); } +.ct-badge--warn { background: var(--warn-tint); color: var(--warn); } +.ct-badge--danger { background: var(--danger-tint); color: var(--danger); } +.ct-badge--info { background: var(--info-tint); color: var(--info); } +.ct-badge--neutral { background: var(--paper-2); color: var(--ink-2); } +.ct-badge--jade { background: var(--jade-tint); color: var(--jade-7); } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-badge-css')) { + const s = document.createElement('style'); + s.id = 'ct-badge-css'; + s.textContent = CSS; + document.head.appendChild(s); + } +})(); +function Badge({ + tone = 'neutral', + dot = false, + children, + style, + ...rest +}) { + return /*#__PURE__*/React.createElement("span", _extends({ + className: `ct-badge ct-badge--${tone}`, + style: style + }, rest), dot ? /*#__PURE__*/React.createElement("span", { + className: "ct-badge__dot" + }) : null, children); +} +Object.assign(__ds_scope, { Badge }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/Badge.jsx", error: String((e && e.message) || e) }); } + +// components/core/Card.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const CSS = ` +.ct-card { + background: var(--surface-card); + border: 1px solid var(--border-hairline); + border-radius: var(--radius-3); + box-shadow: var(--shadow-1); +} +.ct-card--jade { box-shadow: var(--shadow-jade-line), var(--shadow-1); } +.ct-card__header { + display: flex; align-items: baseline; justify-content: space-between; gap: 12px; + padding: 16px 20px 0; +} +.ct-card__title { + font: var(--text-title); + color: var(--ink-1); + margin: 0; +} +.ct-card__overline { + font: var(--text-overline); + letter-spacing: var(--letter-spacing-wide); + text-transform: uppercase; + color: var(--ink-3); + margin: 0 0 6px; +} +.ct-card__body { padding: 14px 20px 18px; } +.ct-card__body--flush { padding: 0; } +.ct-card__footer { + display: flex; align-items: center; gap: 8px; + padding: 12px 20px; + border-top: 1px solid var(--border-hairline); +} +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-card-css')) { + const s = document.createElement('style'); + s.id = 'ct-card-css'; + s.textContent = CSS; + document.head.appendChild(s); + } +})(); +function Card({ + title, + overline, + actions, + footer, + jade = false, + flush = false, + children, + style, + ...rest +}) { + return /*#__PURE__*/React.createElement("section", _extends({ + className: `ct-card${jade ? ' ct-card--jade' : ''}`, + style: style + }, rest), title || overline || actions ? /*#__PURE__*/React.createElement("header", { + className: "ct-card__header" + }, /*#__PURE__*/React.createElement("div", null, overline ? /*#__PURE__*/React.createElement("p", { + className: "ct-card__overline" + }, overline) : null, title ? /*#__PURE__*/React.createElement("h2", { + className: "ct-card__title" + }, title) : null), actions ? /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: '4px', + flexShrink: 0 + } + }, actions) : null) : null, /*#__PURE__*/React.createElement("div", { + className: `ct-card__body${flush ? ' ct-card__body--flush' : ''}` + }, children), footer ? /*#__PURE__*/React.createElement("footer", { + className: "ct-card__footer" + }, footer) : null); +} +Object.assign(__ds_scope, { Card }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/Card.jsx", error: String((e && e.message) || e) }); } + +// components/core/Icon.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +/* Generated from assets/icons/*.svg (lucide-static v0.462.0, ISC). Do not edit paths by hand. */ +const ICONS = { + "activity": "", + "arrow-left": "\n ", + "arrow-right": "\n ", + "arrow-up-right": "\n ", + "bell": "\n ", + "calendar": "\n \n \n ", + "chart-line": "\n ", + "chart-no-axes-gantt": "\n \n ", + "check": "", + "chevron-down": "", + "chevron-left": "", + "chevron-right": "", + "chevron-up": "", + "circle-alert": "\n \n ", + "circle-check": "\n ", + "circle-dashed": "\n \n \n \n \n \n \n ", + "circle-dot": "\n ", + "circle": "", + "clock-3": "\n ", + "clock": "\n ", + "coffee": "\n \n \n ", + "copy": "\n ", + "ellipsis": "\n \n ", + "external-link": "\n \n ", + "eye": "\n ", + "filter": "", + "flag": "\n ", + "gauge": "\n ", + "git-branch": "\n \n \n ", + "git-commit-horizontal": "\n \n ", + "git-merge": "\n \n ", + "git-pull-request": "\n \n \n ", + "history": "\n \n ", + "inbox": "\n ", + "info": "\n \n ", + "keyboard": "\n \n \n \n \n \n \n \n ", + "layers": "\n \n ", + "leaf": "\n ", + "link": "\n ", + "list-filter": "\n \n ", + "list": "\n \n \n \n \n ", + "loader-circle": "", + "message-square": "", + "milestone": "\n \n ", + "minus": "", + "moon": "", + "network": "\n \n \n \n ", + "panel-left": "\n ", + "pause": "\n ", + "pencil": "\n ", + "play": "", + "plus": "\n ", + "refresh-cw": "\n \n \n ", + "search": "\n ", + "send": "\n ", + "settings-2": "\n \n \n ", + "settings": "\n ", + "sparkles": "\n \n \n \n ", + "square-kanban": "\n \n \n ", + "sun": "\n \n \n \n \n \n \n \n ", + "tag": "\n ", + "target": "\n \n ", + "trash-2": "\n \n \n \n ", + "trending-up": "\n ", + "triangle-alert": "\n \n ", + "user": "\n ", + "users": "\n \n \n ", + "x": "\n ", + "zap": "" +}; +function Icon({ + name, + size = 16, + strokeWidth = 1.5, + title, + style, + ...rest +}) { + const inner = ICONS[name]; + if (!inner) { + console.warn('[CommiTea Icon] unknown icon: ' + name); + return null; + } + return /*#__PURE__*/React.createElement("svg", _extends({ + width: size, + height: size, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: strokeWidth, + strokeLinecap: "round", + strokeLinejoin: "round", + "aria-hidden": title ? undefined : true, + role: title ? 'img' : undefined, + "aria-label": title, + style: { + flexShrink: 0, + ...style + }, + dangerouslySetInnerHTML: { + __html: inner + } + }, rest)); +} +const ICON_NAMES = Object.keys(ICONS); +Object.assign(__ds_scope, { Icon, ICON_NAMES }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/Icon.jsx", error: String((e && e.message) || e) }); } + +// components/core/Button.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const CSS = ` +.ct-btn { + display: inline-flex; align-items: center; justify-content: center; gap: 8px; + font: 600 14px/1 var(--font-sans); + border-radius: var(--radius-2); + border: 1px solid transparent; + cursor: pointer; + white-space: nowrap; + transition: background var(--duration-fast) var(--ease-out), + border-color var(--duration-fast) var(--ease-out), + color var(--duration-fast) var(--ease-out); +} +.ct-btn:disabled { opacity: 0.45; cursor: not-allowed; } +.ct-btn--md { height: 34px; padding: 0 14px; } +.ct-btn--sm { height: 28px; padding: 0 10px; font-size: 13px; } +.ct-btn--primary { background: var(--accent); color: var(--text-on-accent); } +.ct-btn--primary:hover:not(:disabled) { background: var(--accent-hover); } +.ct-btn--primary:active:not(:disabled) { background: var(--accent-pressed); } +.ct-btn--secondary { background: var(--surface-card); border-color: var(--line-2); color: var(--ink-1); } +.ct-btn--secondary:hover:not(:disabled) { background: var(--paper-2); } +.ct-btn--secondary:active:not(:disabled) { background: var(--paper-3); } +.ct-btn--ghost { background: transparent; color: var(--ink-2); } +.ct-btn--ghost:hover:not(:disabled) { background: var(--paper-2); color: var(--ink-1); } +.ct-btn--ghost:active:not(:disabled) { background: var(--paper-3); } +.ct-btn--danger { background: var(--danger); color: var(--ink-inverse); } +.ct-btn--danger:hover:not(:disabled) { filter: brightness(0.92); } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-button-css')) { + const s = document.createElement('style'); + s.id = 'ct-button-css'; + s.textContent = CSS; + document.head.appendChild(s); + } +})(); +function Button({ + variant = 'primary', + size = 'md', + icon, + iconRight, + disabled = false, + children, + style, + ...rest +}) { + const iconSize = size === 'sm' ? 15 : 17; + return /*#__PURE__*/React.createElement("button", _extends({ + type: "button", + className: `ct-btn ct-btn--${size} ct-btn--${variant}`, + disabled: disabled, + style: style + }, rest), icon ? /*#__PURE__*/React.createElement(__ds_scope.Icon, { + name: icon, + size: iconSize + }) : null, children, iconRight ? /*#__PURE__*/React.createElement(__ds_scope.Icon, { + name: iconRight, + size: iconSize + }) : null); +} +Object.assign(__ds_scope, { Button }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/Button.jsx", error: String((e && e.message) || e) }); } + +// components/core/IconButton.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const CSS = ` +.ct-iconbtn { + display: inline-flex; align-items: center; justify-content: center; + border-radius: var(--radius-2); + border: 1px solid transparent; + background: transparent; + color: var(--ink-2); + cursor: pointer; + transition: background var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out); +} +.ct-iconbtn:hover:not(:disabled) { background: var(--paper-2); color: var(--ink-1); } +.ct-iconbtn:active:not(:disabled) { background: var(--paper-3); } +.ct-iconbtn:disabled { opacity: 0.45; cursor: not-allowed; } +.ct-iconbtn--md { width: 34px; height: 34px; } +.ct-iconbtn--sm { width: 28px; height: 28px; } +.ct-iconbtn--outline { border-color: var(--line-2); background: var(--surface-card); } +.ct-iconbtn--outline:hover:not(:disabled) { background: var(--paper-2); } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-iconbtn-css')) { + const s = document.createElement('style'); + s.id = 'ct-iconbtn-css'; + s.textContent = CSS; + document.head.appendChild(s); + } +})(); +function IconButton({ + icon, + label, + variant = 'ghost', + size = 'md', + disabled = false, + style, + ...rest +}) { + return /*#__PURE__*/React.createElement("button", _extends({ + type: "button", + className: `ct-iconbtn ct-iconbtn--${size}${variant === 'outline' ? ' ct-iconbtn--outline' : ''}`, + "aria-label": label, + title: label, + disabled: disabled, + style: style + }, rest), /*#__PURE__*/React.createElement(__ds_scope.Icon, { + name: icon, + size: size === 'sm' ? 15 : 17 + })); +} +Object.assign(__ds_scope, { IconButton }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/IconButton.jsx", error: String((e && e.message) || e) }); } + +// components/core/Tabs.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const CSS = ` +.ct-tabs { + display: flex; gap: 2px; + border-bottom: 1px solid var(--border-hairline); +} +.ct-tab { + display: inline-flex; align-items: center; gap: 7px; + font: 500 13.5px/1 var(--font-sans); + color: var(--ink-2); + background: none; border: none; + padding: 10px 12px; + margin-bottom: -1px; + border-bottom: 2px solid transparent; + cursor: pointer; + transition: color var(--duration-fast) var(--ease-out); +} +.ct-tab:hover { color: var(--ink-1); } +.ct-tab--active { + color: var(--ink-1); + font-weight: 600; + border-bottom-color: var(--accent); +} +.ct-tab__count { font: 400 11.5px/1 var(--font-mono); color: var(--ink-3); } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-tabs-css')) { + const s = document.createElement('style'); + s.id = 'ct-tabs-css'; + s.textContent = CSS; + document.head.appendChild(s); + } +})(); +function Tabs({ + items, + active, + onChange, + style, + ...rest +}) { + return /*#__PURE__*/React.createElement("div", _extends({ + className: "ct-tabs", + role: "tablist", + style: style + }, rest), items.map(item => /*#__PURE__*/React.createElement("button", { + key: item.id, + type: "button", + role: "tab", + "aria-selected": item.id === active, + className: `ct-tab${item.id === active ? ' ct-tab--active' : ''}`, + onClick: () => onChange && onChange(item.id) + }, item.icon ? /*#__PURE__*/React.createElement(__ds_scope.Icon, { + name: item.icon, + size: 15 + }) : null, item.label, item.count != null ? /*#__PURE__*/React.createElement("span", { + className: "ct-tab__count" + }, item.count) : null))); +} +Object.assign(__ds_scope, { Tabs }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/Tabs.jsx", error: String((e && e.message) || e) }); } + +// components/core/Tag.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const CSS = ` +.ct-tag { + display: inline-flex; align-items: center; gap: 5px; + font: 500 11.5px/1 var(--font-mono); + letter-spacing: var(--letter-spacing-label); + padding: 4px 9px; + border-radius: var(--radius-round); + white-space: nowrap; +} +.ct-tag--est { background: var(--label-est-bg); color: var(--label-est-text); } +.ct-tag--p1 { background: var(--danger-tint); color: var(--label-p1); } +.ct-tag--p2 { background: var(--warn-tint); color: var(--label-p2); } +.ct-tag--p3 { background: var(--info-tint); color: var(--label-p3); } +.ct-tag--p4 { background: var(--paper-2); color: var(--label-p4); } +.ct-tag--hard { background: var(--label-hard); color: var(--ink-inverse); } +.ct-tag--plain { background: var(--paper-2); color: var(--ink-2); } +.ct-tag__x { + display: inline-flex; padding: 0; margin: 0 -3px 0 0; + background: none; border: none; color: inherit; cursor: pointer; opacity: 0.6; +} +.ct-tag__x:hover { opacity: 1; } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-tag-css')) { + const s = document.createElement('style'); + s.id = 'ct-tag-css'; + s.textContent = CSS; + document.head.appendChild(s); + } +})(); +function toneFor(label) { + if (label.startsWith('est/')) return 'est'; + if (label === 'p/1') return 'p1'; + if (label === 'p/2') return 'p2'; + if (label === 'p/3') return 'p3'; + if (label === 'p/4') return 'p4'; + if (label === 'deadline/hard') return 'hard'; + return 'plain'; +} +function Tag({ + label, + onRemove, + style, + ...rest +}) { + return /*#__PURE__*/React.createElement("span", _extends({ + className: `ct-tag ct-tag--${toneFor(label)}`, + style: style + }, rest), label, onRemove ? /*#__PURE__*/React.createElement("button", { + type: "button", + className: "ct-tag__x", + "aria-label": `Remove ${label}`, + onClick: onRemove + }, /*#__PURE__*/React.createElement(__ds_scope.Icon, { + name: "x", + size: 11, + strokeWidth: 2 + })) : null); +} +Object.assign(__ds_scope, { Tag }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/Tag.jsx", error: String((e && e.message) || e) }); } + +// components/feedback/Dialog.jsx +try { (() => { +const CSS = ` +.ct-dialog-scrim { + position: fixed; inset: 0; + background: rgba(32, 38, 29, 0.4); + display: flex; align-items: center; justify-content: center; + z-index: 100; + animation: ct-dialog-fade var(--duration-base) var(--ease-out); +} +.ct-dialog { + background: var(--surface-card); + border: 1px solid var(--border-hairline); + border-radius: var(--radius-3); + box-shadow: var(--shadow-3); + width: min(480px, calc(100vw - 48px)); + max-height: calc(100vh - 96px); + display: flex; flex-direction: column; + animation: ct-dialog-rise var(--duration-base) var(--ease-out); +} +.ct-dialog__header { + display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; + padding: 20px 20px 12px; + border-bottom: 3px double var(--border-strong); + margin: 0 20px; padding-left: 0; padding-right: 0; +} +.ct-dialog__title { font: var(--text-title); color: var(--ink-1); margin: 0; } +.ct-dialog__body { padding: 16px 20px; overflow-y: auto; font: var(--text-body); color: var(--ink-1); } +.ct-dialog__footer { + display: flex; justify-content: flex-end; gap: 8px; + padding: 12px 20px 20px; +} +@keyframes ct-dialog-fade { from { opacity: 0; } } +@keyframes ct-dialog-rise { from { opacity: 0; transform: translateY(8px); } } +@media (prefers-reduced-motion: reduce) { + .ct-dialog-scrim, .ct-dialog { animation: none; } +} +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-dialog-css')) { + const s = document.createElement('style'); + s.id = 'ct-dialog-css'; + s.textContent = CSS; + document.head.appendChild(s); + } +})(); +function Dialog({ + open, + onClose, + title, + footer, + children, + style +}) { + React.useEffect(() => { + if (!open) return; + const onKey = e => { + if (e.key === 'Escape' && onClose) onClose(); + }; + document.addEventListener('keydown', onKey); + return () => document.removeEventListener('keydown', onKey); + }, [open, onClose]); + if (!open) return null; + return /*#__PURE__*/React.createElement("div", { + className: "ct-dialog-scrim", + onClick: e => { + if (e.target === e.currentTarget && onClose) onClose(); + } + }, /*#__PURE__*/React.createElement("div", { + className: "ct-dialog", + role: "dialog", + "aria-modal": "true", + style: style + }, /*#__PURE__*/React.createElement("header", { + className: "ct-dialog__header" + }, /*#__PURE__*/React.createElement("h2", { + className: "ct-dialog__title" + }, title), onClose ? /*#__PURE__*/React.createElement(__ds_scope.IconButton, { + icon: "x", + label: "Close", + size: "sm", + onClick: onClose + }) : null), /*#__PURE__*/React.createElement("div", { + className: "ct-dialog__body" + }, children), footer ? /*#__PURE__*/React.createElement("footer", { + className: "ct-dialog__footer" + }, footer) : null)); +} +Object.assign(__ds_scope, { Dialog }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/feedback/Dialog.jsx", error: String((e && e.message) || e) }); } + +// components/feedback/Toast.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const CSS = ` +.ct-toast { + display: flex; align-items: flex-start; gap: 10px; + background: var(--surface-card); + border: 1px solid var(--border-hairline); + border-radius: var(--radius-2); + box-shadow: var(--shadow-2); + padding: 12px 14px; + max-width: 420px; + font: var(--text-body); + color: var(--ink-1); + animation: ct-toast-in var(--duration-slow) var(--ease-out); +} +.ct-toast__icon { display: flex; margin-top: 1px; } +.ct-toast--ok .ct-toast__icon { color: var(--ok); } +.ct-toast--warn .ct-toast__icon { color: var(--warn); } +.ct-toast--danger .ct-toast__icon { color: var(--danger); } +.ct-toast--info .ct-toast__icon { color: var(--info); } +.ct-toast__content { flex: 1; min-width: 0; } +.ct-toast__title { font: var(--text-body-strong); margin: 0 0 2px; } +@keyframes ct-toast-in { from { opacity: 0; transform: translateY(6px); } } +@media (prefers-reduced-motion: reduce) { .ct-toast { animation: none; } } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-toast-css')) { + const s = document.createElement('style'); + s.id = 'ct-toast-css'; + s.textContent = CSS; + document.head.appendChild(s); + } +})(); +const TOAST_ICONS = { + ok: 'circle-check', + warn: 'triangle-alert', + danger: 'circle-alert', + info: 'info' +}; +function Toast({ + tone = 'info', + title, + onDismiss, + children, + style, + ...rest +}) { + return /*#__PURE__*/React.createElement("div", _extends({ + className: `ct-toast ct-toast--${tone}`, + role: "status", + style: style + }, rest), /*#__PURE__*/React.createElement("span", { + className: "ct-toast__icon" + }, /*#__PURE__*/React.createElement(__ds_scope.Icon, { + name: TOAST_ICONS[tone], + size: 16 + })), /*#__PURE__*/React.createElement("div", { + className: "ct-toast__content" + }, title ? /*#__PURE__*/React.createElement("p", { + className: "ct-toast__title" + }, title) : null, children), onDismiss ? /*#__PURE__*/React.createElement(__ds_scope.IconButton, { + icon: "x", + label: "Dismiss", + size: "sm", + onClick: onDismiss + }) : null); +} +Object.assign(__ds_scope, { Toast }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/feedback/Toast.jsx", error: String((e && e.message) || e) }); } + +// components/feedback/Tooltip.jsx +try { (() => { +const CSS = ` +.ct-tooltip-wrap { position: relative; display: inline-flex; } +.ct-tooltip { + position: absolute; bottom: calc(100% + 7px); left: 50%; + transform: translateX(-50%) translateY(2px); + background: var(--ink-1); + color: var(--ink-inverse); + font: 500 12px/1.4 var(--font-sans); + padding: 5px 9px; + border-radius: var(--radius-1); + white-space: nowrap; + pointer-events: none; + opacity: 0; + transition: opacity var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out); + z-index: 50; +} +.ct-tooltip--bottom { bottom: auto; top: calc(100% + 7px); transform: translateX(-50%) translateY(-2px); } +.ct-tooltip-wrap:hover .ct-tooltip, +.ct-tooltip-wrap:focus-within .ct-tooltip { + opacity: 1; + transform: translateX(-50%) translateY(0); +} +.ct-tooltip code { font: 500 11px var(--font-mono); } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-tooltip-css')) { + const s = document.createElement('style'); + s.id = 'ct-tooltip-css'; + s.textContent = CSS; + document.head.appendChild(s); + } +})(); +function Tooltip({ + content, + side = 'top', + children, + style +}) { + return /*#__PURE__*/React.createElement("span", { + className: "ct-tooltip-wrap", + style: style + }, children, /*#__PURE__*/React.createElement("span", { + className: `ct-tooltip${side === 'bottom' ? ' ct-tooltip--bottom' : ''}`, + role: "tooltip" + }, content)); +} +Object.assign(__ds_scope, { Tooltip }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/feedback/Tooltip.jsx", error: String((e && e.message) || e) }); } + +// components/forms/Checkbox.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const CSS = ` +.ct-check { display: inline-flex; align-items: center; gap: 9px; cursor: pointer; font: var(--text-body); color: var(--ink-1); } +.ct-check--disabled { opacity: 0.5; cursor: not-allowed; } +.ct-check__input { position: absolute; opacity: 0; width: 0; height: 0; } +.ct-check__box { + width: 16px; height: 16px; flex-shrink: 0; + display: inline-flex; align-items: center; justify-content: center; + background: var(--surface-card); + border: 1px solid var(--line-2); + border-radius: var(--radius-1); + color: transparent; + transition: background var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out); +} +.ct-check:hover:not(.ct-check--disabled) .ct-check__box { border-color: var(--border-strong); } +.ct-check__input:checked + .ct-check__box { background: var(--accent); border-color: var(--accent); color: var(--ink-inverse); } +.ct-check__input:focus-visible + .ct-check__box { outline: 2px solid var(--focus-ring); outline-offset: 2px; } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-check-css')) { + const s = document.createElement('style'); + s.id = 'ct-check-css'; + s.textContent = CSS; + document.head.appendChild(s); + } +})(); +function Checkbox({ + label, + checked, + onChange, + disabled = false, + style, + ...rest +}) { + return /*#__PURE__*/React.createElement("label", { + className: `ct-check${disabled ? ' ct-check--disabled' : ''}`, + style: style + }, /*#__PURE__*/React.createElement("input", _extends({ + type: "checkbox", + className: "ct-check__input", + checked: checked, + onChange: onChange, + disabled: disabled + }, rest)), /*#__PURE__*/React.createElement("span", { + className: "ct-check__box" + }, /*#__PURE__*/React.createElement(__ds_scope.Icon, { + name: "check", + size: 12, + strokeWidth: 2.5 + })), label ? /*#__PURE__*/React.createElement("span", null, label) : null); +} +Object.assign(__ds_scope, { Checkbox }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Checkbox.jsx", error: String((e && e.message) || e) }); } + +// components/forms/Input.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const CSS = ` +.ct-field { display: flex; flex-direction: column; gap: 6px; } +.ct-field__label { font: 600 13px/1.2 var(--font-sans); color: var(--ink-1); } +.ct-field__wrap { position: relative; display: flex; align-items: center; } +.ct-field__icon { position: absolute; left: 10px; color: var(--ink-3); pointer-events: none; display: flex; } +.ct-input { + width: 100%; height: 34px; + font: var(--text-body); + color: var(--ink-1); + background: var(--surface-card); + border: 1px solid var(--line-2); + border-radius: var(--radius-2); + padding: 0 12px; + transition: border-color var(--duration-fast) var(--ease-out); +} +.ct-input::placeholder { color: var(--ink-3); } +.ct-input:hover:not(:disabled):not(:focus) { border-color: var(--border-strong); background: var(--paper-1); } +.ct-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); } +.ct-input:disabled { opacity: 0.5; background: var(--paper-2); cursor: not-allowed; } +.ct-input--icon { padding-left: 32px; } +.ct-input--error { border-color: var(--danger); } +.ct-input--error:focus { border-color: var(--danger); box-shadow: 0 0 0 1px var(--danger); } +.ct-input--mono { font: var(--text-data); } +.ct-field__hint { font: var(--text-caption); color: var(--ink-3); margin: 0; } +.ct-field__error { font: var(--text-caption); color: var(--danger); margin: 0; } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-input-css')) { + const s = document.createElement('style'); + s.id = 'ct-input-css'; + s.textContent = CSS; + document.head.appendChild(s); + } +})(); +function Input({ + label, + hint, + error, + icon, + mono = false, + style, + ...rest +}) { + const cls = ['ct-input', icon ? 'ct-input--icon' : '', error ? 'ct-input--error' : '', mono ? 'ct-input--mono' : ''].filter(Boolean).join(' '); + return /*#__PURE__*/React.createElement("div", { + className: "ct-field", + style: style + }, label ? /*#__PURE__*/React.createElement("label", { + className: "ct-field__label" + }, label) : null, /*#__PURE__*/React.createElement("div", { + className: "ct-field__wrap" + }, icon ? /*#__PURE__*/React.createElement("span", { + className: "ct-field__icon" + }, /*#__PURE__*/React.createElement(__ds_scope.Icon, { + name: icon, + size: 15 + })) : null, /*#__PURE__*/React.createElement("input", _extends({ + className: cls + }, rest))), error ? /*#__PURE__*/React.createElement("p", { + className: "ct-field__error" + }, error) : hint ? /*#__PURE__*/React.createElement("p", { + className: "ct-field__hint" + }, hint) : null); +} +Object.assign(__ds_scope, { Input }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Input.jsx", error: String((e && e.message) || e) }); } + +// components/forms/Radio.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const CSS = ` +.ct-radio { display: inline-flex; align-items: center; gap: 9px; cursor: pointer; font: var(--text-body); color: var(--ink-1); } +.ct-radio--disabled { opacity: 0.5; cursor: not-allowed; } +.ct-radio__input { position: absolute; opacity: 0; width: 0; height: 0; } +.ct-radio__dot { + width: 16px; height: 16px; flex-shrink: 0; + border: 1px solid var(--line-2); + border-radius: 50%; + background: var(--surface-card); + display: inline-flex; align-items: center; justify-content: center; + transition: border-color var(--duration-fast) var(--ease-out); +} +.ct-radio__dot::after { + content: ''; + width: 8px; height: 8px; border-radius: 50%; + background: transparent; + transition: background var(--duration-fast) var(--ease-out); +} +.ct-radio:hover:not(.ct-radio--disabled) .ct-radio__dot { border-color: var(--border-strong); } +.ct-radio__input:checked + .ct-radio__dot { border-color: var(--accent); } +.ct-radio__input:checked + .ct-radio__dot::after { background: var(--accent); } +.ct-radio__input:focus-visible + .ct-radio__dot { outline: 2px solid var(--focus-ring); outline-offset: 2px; } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-radio-css')) { + const s = document.createElement('style'); + s.id = 'ct-radio-css'; + s.textContent = CSS; + document.head.appendChild(s); + } +})(); +function Radio({ + label, + checked, + onChange, + name, + value, + disabled = false, + style, + ...rest +}) { + return /*#__PURE__*/React.createElement("label", { + className: `ct-radio${disabled ? ' ct-radio--disabled' : ''}`, + style: style + }, /*#__PURE__*/React.createElement("input", _extends({ + type: "radio", + className: "ct-radio__input", + checked: checked, + onChange: onChange, + name: name, + value: value, + disabled: disabled + }, rest)), /*#__PURE__*/React.createElement("span", { + className: "ct-radio__dot" + }), label ? /*#__PURE__*/React.createElement("span", null, label) : null); +} +Object.assign(__ds_scope, { Radio }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Radio.jsx", error: String((e && e.message) || e) }); } + +// components/forms/Select.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const CSS = ` +.ct-select-field { display: flex; flex-direction: column; gap: 6px; } +.ct-select-field__label { font: 600 13px/1.2 var(--font-sans); color: var(--ink-1); } +.ct-select__wrap { position: relative; display: flex; align-items: center; } +.ct-select { + width: 100%; height: 34px; + font: var(--text-body); + color: var(--ink-1); + background: var(--surface-card); + border: 1px solid var(--line-2); + border-radius: var(--radius-2); + padding: 0 30px 0 12px; + appearance: none; + cursor: pointer; + transition: border-color var(--duration-fast) var(--ease-out); +} +.ct-select:hover:not(:disabled):not(:focus) { border-color: var(--border-strong); } +.ct-select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); } +.ct-select:disabled { opacity: 0.5; cursor: not-allowed; } +.ct-select__chevron { position: absolute; right: 10px; color: var(--ink-3); pointer-events: none; display: flex; } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-select-css')) { + const s = document.createElement('style'); + s.id = 'ct-select-css'; + s.textContent = CSS; + document.head.appendChild(s); + } +})(); +function Select({ + label, + options, + style, + ...rest +}) { + return /*#__PURE__*/React.createElement("div", { + className: "ct-select-field", + style: style + }, label ? /*#__PURE__*/React.createElement("label", { + className: "ct-select-field__label" + }, label) : null, /*#__PURE__*/React.createElement("div", { + className: "ct-select__wrap" + }, /*#__PURE__*/React.createElement("select", _extends({ + className: "ct-select" + }, rest), options.map(o => /*#__PURE__*/React.createElement("option", { + key: o.value, + value: o.value + }, o.label))), /*#__PURE__*/React.createElement("span", { + className: "ct-select__chevron" + }, /*#__PURE__*/React.createElement(__ds_scope.Icon, { + name: "chevron-down", + size: 15 + })))); +} +Object.assign(__ds_scope, { Select }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Select.jsx", error: String((e && e.message) || e) }); } + +// components/forms/Switch.jsx +try { (() => { +function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } +const CSS = ` +.ct-switch { display: inline-flex; align-items: center; gap: 9px; cursor: pointer; font: var(--text-body); color: var(--ink-1); } +.ct-switch--disabled { opacity: 0.5; cursor: not-allowed; } +.ct-switch__input { position: absolute; opacity: 0; width: 0; height: 0; } +.ct-switch__track { + width: 34px; height: 20px; flex-shrink: 0; + border-radius: var(--radius-round); + background: var(--paper-3); + border: 1px solid var(--line-2); + position: relative; + transition: background var(--duration-base) var(--ease-out), border-color var(--duration-base) var(--ease-out); +} +.ct-switch__track::after { + content: ''; + position: absolute; top: 2px; left: 2px; + width: 14px; height: 14px; border-radius: 50%; + background: var(--surface-card); + box-shadow: var(--shadow-1); + transition: transform var(--duration-base) var(--ease-out); +} +.ct-switch__input:checked + .ct-switch__track { background: var(--accent); border-color: var(--accent); } +.ct-switch__input:checked + .ct-switch__track::after { transform: translateX(14px); } +.ct-switch__input:focus-visible + .ct-switch__track { outline: 2px solid var(--focus-ring); outline-offset: 2px; } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-switch-css')) { + const s = document.createElement('style'); + s.id = 'ct-switch-css'; + s.textContent = CSS; + document.head.appendChild(s); + } +})(); +function Switch({ + label, + checked, + onChange, + disabled = false, + style, + ...rest +}) { + return /*#__PURE__*/React.createElement("label", { + className: `ct-switch${disabled ? ' ct-switch--disabled' : ''}`, + style: style + }, /*#__PURE__*/React.createElement("input", _extends({ + type: "checkbox", + role: "switch", + className: "ct-switch__input", + checked: checked, + onChange: onChange, + disabled: disabled + }, rest)), /*#__PURE__*/React.createElement("span", { + className: "ct-switch__track" + }), label ? /*#__PURE__*/React.createElement("span", null, label) : null); +} +Object.assign(__ds_scope, { Switch }); +})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Switch.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/BoardScreen.jsx +try { (() => { +// Board — kanban over inferred lifecycle, with Gantt/Dependencies stubs +function BoardScreen({ + onOpenIssue +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Card, + Tag, + Badge, + Tabs, + IconButton, + Input, + Icon + } = DS; + const d = window.CT_DATA; + const [tab, setTab] = React.useState('board'); + const [query, setQuery] = React.useState(''); + const q = query.trim().toLowerCase(); + const filtered = d.columns.map(c => ({ + ...c, + issues: q ? c.issues.filter(i => (i.title + ' #' + i.id).toLowerCase().includes(q)) : c.issues + })); + const anyMatch = filtered.some(c => c.issues.length > 0); + const openCount = d.columns.reduce((n, c) => n + (c.id === 'done' ? 0 : c.issues.length), 0); + const IssueCard = ({ + issue + }) => /*#__PURE__*/React.createElement("div", { + onClick: () => onOpenIssue(issue), + style: { + background: 'var(--surface-card)', + border: '1px solid var(--line-1)', + borderRadius: 'var(--radius-2)', + padding: '10px 12px', + cursor: 'pointer', + boxShadow: 'var(--shadow-1)', + display: 'flex', + flexDirection: 'column', + gap: 8 + }, + onMouseEnter: e => { + e.currentTarget.style.borderColor = 'var(--line-2)'; + }, + onMouseLeave: e => { + e.currentTarget.style.borderColor = 'var(--line-1)'; + } + }, /*#__PURE__*/React.createElement("div", { + style: { + font: 'var(--text-small)', + fontWeight: 500, + color: 'var(--ink-1)' + } + }, issue.title), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 6, + flexWrap: 'wrap' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-3)' + } + }, "#", issue.id), issue.labels.map(l => /*#__PURE__*/React.createElement(Tag, { + key: l, + label: l, + style: { + transform: 'scale(0.95)', + transformOrigin: 'left center' + } + })), issue.days ? /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--warn)' + } + }, issue.days) : null, issue.pr ? /*#__PURE__*/React.createElement("span", { + style: { + display: 'inline-flex', + alignItems: 'center', + gap: 3, + font: '400 11px var(--font-mono)', + color: 'var(--info)' + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "git-pull-request", + size: 12 + }), issue.pr) : null, /*#__PURE__*/React.createElement("span", { + style: { + marginLeft: 'auto', + width: 20, + height: 20, + borderRadius: '50%', + background: 'var(--spruce-2)', + color: 'var(--accent-text)', + font: '600 9px/20px var(--font-sans)', + textAlign: 'center', + flexShrink: 0 + } + }, issue.who))); + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 14, + height: '100%', + minHeight: 0 + } + }, /*#__PURE__*/React.createElement("header", { + style: { + display: 'flex', + alignItems: 'baseline', + justifyContent: 'space-between', + gap: 12 + } + }, /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-display)', + color: 'var(--ink-1)', + margin: 0, + whiteSpace: 'nowrap' + } + }, "The pot"), /*#__PURE__*/React.createElement("div", { + style: { + width: 240 + } + }, /*#__PURE__*/React.createElement(Input, { + icon: "search", + placeholder: "Search the pot\u2026", + value: query, + onChange: e => setQuery(e.target.value) + }))), /*#__PURE__*/React.createElement(Tabs, { + items: [{ + id: 'board', + label: 'Board', + icon: 'square-kanban', + count: openCount + }, { + id: 'gantt', + label: 'Gantt', + icon: 'chart-no-axes-gantt' + }, { + id: 'deps', + label: 'Dependencies', + icon: 'network' + }], + active: tab, + onChange: setTab + }), tab === 'board' ? anyMatch ? /*#__PURE__*/React.createElement("div", { + style: { + display: 'grid', + gridTemplateColumns: 'repeat(5, 1fr)', + gap: 12, + alignItems: 'start', + flex: 1, + minHeight: 0, + overflow: 'auto' + } + }, filtered.map(col => /*#__PURE__*/React.createElement("div", { + key: col.id, + style: { + display: 'flex', + flexDirection: 'column', + gap: 8 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 6, + padding: '2px 2px 4px', + borderBottom: '1px solid var(--line-1)', + whiteSpace: 'nowrap' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-overline)', + letterSpacing: 'var(--letter-spacing-wide)', + textTransform: 'uppercase', + color: 'var(--ink-2)' + } + }, col.label), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)' + } + }, col.issues.length)), col.issues.map(i => /*#__PURE__*/React.createElement(IssueCard, { + key: i.id, + issue: i + }))))) : /*#__PURE__*/React.createElement("div", { + style: { + flex: 1, + border: '1px dashed var(--line-2)', + borderRadius: 'var(--radius-3)' + } + }, /*#__PURE__*/React.createElement(window.EmptyState, { + icon: "search", + title: "Nothing by that name", + line: `The pot holds ${d.columns.reduce((n, c) => n + c.issues.length, 0)} issues; none of them answer to “${query.trim()}”.` + })) : tab === 'deps' ? /*#__PURE__*/React.createElement(window.DepsGraph, { + onOpenIssue: onOpenIssue + }) : /*#__PURE__*/React.createElement(window.GanttView, { + onOpenIssue: onOpenIssue + })); +} +Object.assign(window, { + BoardScreen +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/BoardScreen.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/CalibrationScreen.jsx +try { (() => { +// Calibration report — estimate-vs-actual evidence behind the cones +function CalibrationScreen({ + onBack +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Card, + Badge, + Icon + } = DS; + const c = window.CT_DATA.calibration; + + // scatter chart geometry + const W = 420, + H = 300, + pad = { + l: 36, + r: 16, + t: 14, + b: 30 + }; + const maxD = 9; + const X = d => pad.l + d / maxD * (W - pad.l - pad.r); + const Y = d => H - pad.b - d / maxD * (H - pad.t - pad.b); + const BiasBar = ({ + bias + }) => { + if (bias == null) return /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)' + } + }, "n too small"); + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 8, + flex: 1 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + position: 'relative', + flex: 1, + height: 10, + background: 'var(--paper-2)', + borderRadius: 3 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + position: 'absolute', + left: '30%', + top: -2, + bottom: -2, + width: 1.5, + background: 'var(--line-2)' + } + }), /*#__PURE__*/React.createElement("div", { + style: { + position: 'absolute', + left: '30%', + top: 1.5, + height: 7, + width: `${Math.min(bias * 1.6, 66)}%`, + background: bias > 15 ? 'var(--warn)' : 'var(--ok)', + borderRadius: '0 3px 3px 0', + opacity: 0.75 + } + })), /*#__PURE__*/React.createElement("span", { + style: { + font: '500 11.5px var(--font-mono)', + color: bias > 15 ? 'var(--warn)' : 'var(--ok)', + width: 42, + textAlign: 'right' + } + }, "+", bias, "%")); + }; + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 16 + } + }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", { + type: "button", + onClick: onBack, + style: { + display: 'inline-flex', + alignItems: 'center', + gap: 6, + background: 'none', + border: 'none', + font: '500 12.5px var(--font-sans)', + color: 'var(--ink-2)', + cursor: 'pointer', + padding: '2px 0', + marginBottom: 10 + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "arrow-left", + size: 14 + }), " Runway"), /*#__PURE__*/React.createElement("header", { + style: { + borderBottom: 'var(--rule-double)', + paddingBottom: 14, + display: 'flex', + alignItems: 'baseline', + justifyContent: 'space-between', + gap: 12 + } + }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-display)', + color: 'var(--ink-1)', + margin: 0 + } + }, "Calibration"), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-data)', + color: 'var(--ink-3)', + margin: '6px 0 0', + whiteSpace: 'nowrap' + } + }, c.n, " closed issues with estimates \xB7 evidence, not opinion")), /*#__PURE__*/React.createElement(Badge, { + tone: "ok", + dot: true + }, "curve active \xB7 n \u2265 20"))), /*#__PURE__*/React.createElement("div", { + style: { + display: 'grid', + gridTemplateColumns: '1fr 1fr', + gap: 14, + alignItems: 'start' + } + }, /*#__PURE__*/React.createElement(Card, { + overline: "Estimate vs actual", + title: "The shape of hope", + jade: true + }, /*#__PURE__*/React.createElement("svg", { + width: "100%", + viewBox: `0 0 ${W} ${H}`, + style: { + display: 'block' + } + }, [1, 3, 5, 8].map(d => /*#__PURE__*/React.createElement(React.Fragment, { + key: d + }, /*#__PURE__*/React.createElement("line", { + x1: X(d), + x2: X(d), + y1: pad.t, + y2: H - pad.b, + stroke: "var(--line-1)", + strokeWidth: "1" + }), /*#__PURE__*/React.createElement("text", { + x: X(d), + y: H - 12, + textAnchor: "middle", + style: { + font: '400 10px var(--font-mono)', + fill: 'var(--ink-3)' + } + }, d, "d"), /*#__PURE__*/React.createElement("line", { + x1: pad.l, + x2: W - pad.r, + y1: Y(d), + y2: Y(d), + stroke: "var(--line-1)", + strokeWidth: "1" + }), /*#__PURE__*/React.createElement("text", { + x: pad.l - 6, + y: Y(d) + 3, + textAnchor: "end", + style: { + font: '400 10px var(--font-mono)', + fill: 'var(--ink-3)' + } + }, d, "d"))), /*#__PURE__*/React.createElement("line", { + x1: X(0), + y1: Y(0), + x2: X(maxD), + y2: Y(maxD), + stroke: "var(--line-2)", + strokeWidth: "1.2", + strokeDasharray: "4 4" + }), /*#__PURE__*/React.createElement("text", { + x: X(7.6), + y: Y(7.6) + 14, + style: { + font: '400 10px var(--font-mono)', + fill: 'var(--ink-3)' + } + }, "honest"), /*#__PURE__*/React.createElement("line", { + x1: X(0), + y1: Y(0), + x2: X(maxD / c.fit), + y2: Y(maxD), + stroke: "var(--cone-line)", + strokeWidth: "1.6" + }), /*#__PURE__*/React.createElement("text", { + x: X(4.1), + y: Y(4.1 * c.fit) - 8, + style: { + font: '500 10px var(--font-mono)', + fill: 'var(--cone-line)' + } + }, "you \xB7 \xD7", c.fit), c.scatter.map(([e, a], i) => /*#__PURE__*/React.createElement("circle", { + key: i, + cx: X(e) + (i % 5 - 2) * 3, + cy: Y(a), + r: "3", + fill: "var(--accent)", + opacity: "0.55" + }))), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-caption)', + color: 'var(--ink-3)', + margin: '8px 0 0' + } + }, "estimated (x) vs actual days (y) \xB7 actuals inferred from git events, never tracked")), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 14 + } + }, /*#__PURE__*/React.createElement(Card, { + overline: "Bias by estimate label", + flush: true + }, /*#__PURE__*/React.createElement("div", null, c.labels.map((r, i) => /*#__PURE__*/React.createElement("div", { + key: r.label, + style: { + display: 'flex', + alignItems: 'center', + gap: 14, + padding: '10px 20px', + borderTop: i === 0 ? 'none' : '1px solid var(--line-1)' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: '500 11.5px var(--font-mono)', + color: 'var(--ink-1)', + width: 52 + } + }, r.label), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)', + width: 66 + } + }, "n=", r.n, " \xB7 ", r.median), /*#__PURE__*/React.createElement(BiasBar, { + bias: r.bias + }))))), /*#__PURE__*/React.createElement(Card, { + overline: "By person", + flush: true + }, /*#__PURE__*/React.createElement("div", null, c.people.map((p, i) => /*#__PURE__*/React.createElement("div", { + key: p.who, + style: { + display: 'flex', + alignItems: 'center', + gap: 12, + padding: '11px 20px', + borderTop: i === 0 ? 'none' : '1px solid var(--line-1)' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 24, + height: 24, + borderRadius: '50%', + background: 'var(--spruce-2)', + color: 'var(--accent-text)', + font: '600 9px/24px var(--font-sans)', + textAlign: 'center', + flexShrink: 0 + } + }, p.who.split(' ').map(w => w[0]).join('')), /*#__PURE__*/React.createElement("div", { + style: { + flex: 1, + minWidth: 0 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-body-strong)', + color: 'var(--ink-1)' + } + }, p.who), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)' + } + }, " \xB7 n=", p.n, " \xB7 ", p.note)), /*#__PURE__*/React.createElement("span", { + style: { + font: '500 12px var(--font-mono)', + color: p.bias > 15 ? 'var(--warn)' : 'var(--ok)' + } + }, "+", p.bias, "%"))))), /*#__PURE__*/React.createElement(Card, { + overline: "What this does to your forecasts" + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 10, + flexWrap: 'wrap' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: '400 12.5px var(--font-mono)', + color: 'var(--ink-2)', + whiteSpace: 'nowrap' + } + }, c.effect.raw), /*#__PURE__*/React.createElement(Icon, { + name: "arrow-right", + size: 14, + style: { + color: 'var(--ink-3)' + } + }), /*#__PURE__*/React.createElement("span", { + style: { + font: '500 12.5px var(--font-mono)', + color: 'var(--ink-1)', + whiteSpace: 'nowrap' + } + }, c.effect.banded)), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: '10px 0 0' + } + }, "You are not bad at estimating; you are optimistic in a very stable way. Stable, I can work with."))))); +} +Object.assign(window, { + CalibrationScreen +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/CalibrationScreen.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/CaptureScreen.jsx +try { (() => { +// Capture interview — braindump → interview → approved ticket set (< 2 min) +function CaptureScreen({ + onDone +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Button, + Card, + Tag, + Badge, + Select, + Icon + } = DS; + const [stage, setStage] = React.useState('dump'); // dump | interview | review | filed + const [dump, setDump] = React.useState('auth is flaky \u2014 token refresh dies silently, sometimes session storage goes stale. ' + 'also the webhook debounce thing keeps double-firing. and we owe docs for auth setup'); + const [qi, setQi] = React.useState(0); + const [log, setLog] = React.useState([]); + const [split, setSplit] = React.useState(null); + const [webEst, setWebEst] = React.useState(null); + const [secs, setSecs] = React.useState(0); + const running = stage === 'interview' || stage === 'review'; + React.useEffect(() => { + if (!running) return; + const t = setInterval(() => setSecs(s => s + 1), 1000); + return () => clearInterval(t); + }, [running]); + const clock = `${Math.floor(secs / 60)}:${String(secs % 60).padStart(2, '0')}`; + const QUESTIONS = [{ + q: 'The auth work \u2014 one ticket, or shall I split token refresh from session storage? They fail differently.', + chips: ['One ticket', 'Split them'], + set: a => setSplit(a === 'Split them') + }, { + q: 'The webhook double-fire \u2014 how long? I should mention your "quick" has averaged two days.', + chips: ['est/1d', 'est/2d', 'est/3d'], + set: a => setWebEst(a) + }, { + q: 'Milestone Beta, I presume? It has room, provided the auth work stays under four days.', + chips: ['Beta', 'New milestone'], + set: () => {} + }]; + const answer = a => { + QUESTIONS[qi].set(a); + setLog(l => [...l, { + q: QUESTIONS[qi].q, + a + }]); + if (qi + 1 < QUESTIONS.length) setQi(qi + 1);else setStage('review'); + }; + + // draft tickets build as the interview progresses + const tickets = []; + if (split === true) { + tickets.push({ + title: 'Token refresh: retry with backoff', + est: 'est/2d', + p: 'p/2' + }); + tickets.push({ + title: 'Session storage: stale reads on wake', + est: 'est/1d', + p: 'p/3' + }); + } else if (split === false) { + tickets.push({ + title: 'Auth: token refresh + session storage', + est: 'est/3d', + p: 'p/2' + }); + } + if (webEst) tickets.push({ + title: 'Webhook debounce: double-fire guard', + est: webEst, + p: 'p/1', + dep: 'blocked by auth work' + }); + if (stage === 'review' || stage === 'filed') { + tickets.push({ + title: 'Docs: auth setup guide', + est: 'est/1d', + p: 'p/4', + byReginald: true + }); + } + const totalDays = tickets.reduce((n, t) => n + parseInt(t.est.replace('est/', '')), 0); + const estOptions = ['est/1d', 'est/2d', 'est/3d', 'est/5d', 'est/8d'].map(v => ({ + value: v, + label: v + })); + const pOptions = ['p/1', 'p/2', 'p/3', 'p/4'].map(v => ({ + value: v, + label: v + })); + const Tray = ({ + editable + }) => /*#__PURE__*/React.createElement(Card, { + overline: "The tray", + title: tickets.length ? `${tickets.length} draft${tickets.length > 1 ? 's' : ''}` : 'Empty, for now', + flush: true + }, /*#__PURE__*/React.createElement("div", null, tickets.length === 0 ? /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-3)', + margin: 0, + padding: '14px 20px 18px' + } + }, "Tickets appear here as we talk.") : tickets.map((t, i) => /*#__PURE__*/React.createElement("div", { + key: t.title, + style: { + display: 'flex', + flexDirection: 'column', + gap: 8, + padding: '12px 20px', + borderTop: i === 0 ? 'none' : '1px solid var(--line-1)' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + font: 'var(--text-body-strong)', + color: 'var(--ink-1)' + } + }, t.title), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 8, + flexWrap: 'wrap' + } + }, editable ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Select, { + options: estOptions, + defaultValue: t.est, + style: { + width: 96 + } + }), /*#__PURE__*/React.createElement(Select, { + options: pOptions, + defaultValue: t.p, + style: { + width: 76 + } + })) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Tag, { + label: t.est + }), /*#__PURE__*/React.createElement(Tag, { + label: t.p + })), t.dep ? /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)', + display: 'inline-flex', + alignItems: 'center', + gap: 4 + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "network", + size: 11 + }), t.dep) : null, t.byReginald ? /*#__PURE__*/React.createElement(Badge, { + tone: "jade" + }, "added by Reginald") : null))))); + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 16 + } + }, /*#__PURE__*/React.createElement("header", { + style: { + borderBottom: 'var(--rule-double)', + paddingBottom: 14, + display: 'flex', + alignItems: 'baseline', + justifyContent: 'space-between', + gap: 12 + } + }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-display)', + color: 'var(--ink-1)', + margin: 0 + } + }, "Capture"), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-data)', + color: 'var(--ink-3)', + margin: '6px 0 0', + whiteSpace: 'nowrap' + } + }, "braindump \u2192 approved tickets")), stage !== 'dump' ? /*#__PURE__*/React.createElement("div", { + style: { + textAlign: 'right' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + font: '400 24px/1 var(--font-serif-display)', + color: secs > 120 ? 'var(--warn)' : 'var(--ink-1)' + } + }, clock), /*#__PURE__*/React.createElement("div", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)', + marginTop: 4 + } + }, "budget 2:00")) : null), stage === 'dump' ? /*#__PURE__*/React.createElement(Card, { + overline: "Braindump", + title: "Tell me what you're planning", + jade: true + }, /*#__PURE__*/React.createElement("textarea", { + value: dump, + onChange: e => setDump(e.target.value), + rows: 5, + style: { + width: '100%', + resize: 'vertical', + font: 'var(--text-body)', + color: 'var(--ink-1)', + background: 'var(--paper-0)', + border: '1px solid var(--line-2)', + borderRadius: 'var(--radius-2)', + padding: '10px 12px', + outline: 'none', + lineHeight: 1.55 + } + }), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: '10px 0 14px' + } + }, "Sentences, fragments, grievances \u2014 all welcome. I'll sort it into tickets and only ask what I can't infer."), /*#__PURE__*/React.createElement(Button, { + icon: "sparkles", + onClick: () => setStage('interview') + }, "Brew tickets")) : null, stage === 'interview' ? /*#__PURE__*/React.createElement("div", { + style: { + display: 'grid', + gridTemplateColumns: '1.3fr 1fr', + gap: 14, + alignItems: 'start' + } + }, /*#__PURE__*/React.createElement(Card, { + overline: `Interview \u00b7 ${qi + 1} of ${QUESTIONS.length}`, + jade: true + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 14 + } + }, log.map((e, i) => /*#__PURE__*/React.createElement("div", { + key: i, + style: { + display: 'flex', + flexDirection: 'column', + gap: 6, + opacity: 0.66 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)' + } + }, e.q), /*#__PURE__*/React.createElement("span", { + style: { + alignSelf: 'flex-start', + font: 'var(--text-small)', + background: 'var(--paper-2)', + borderRadius: 'var(--radius-round)', + padding: '4px 11px' + } + }, e.a))), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent-lg)', + color: 'var(--ink-1)', + margin: 0 + } + }, QUESTIONS[qi].q), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: 8, + flexWrap: 'wrap' + } + }, QUESTIONS[qi].chips.map(c => /*#__PURE__*/React.createElement(Button, { + key: c, + variant: "secondary", + size: "sm", + onClick: () => answer(c) + }, c))))), /*#__PURE__*/React.createElement(Tray, null)) : null, stage === 'review' ? /*#__PURE__*/React.createElement("div", { + style: { + display: 'grid', + gridTemplateColumns: '1fr 1.1fr', + gap: 14, + alignItems: 'start' + } + }, /*#__PURE__*/React.createElement(Card, { + overline: "Consequence", + title: `${tickets.length} tickets \u00b7 ~${totalDays}d of work`, + jade: true, + footer: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, { + onClick: () => setStage('filed') + }, "Approve all"), /*#__PURE__*/React.createElement(Button, { + variant: "ghost", + onClick: () => { + setStage('dump'); + setQi(0); + setLog([]); + setSplit(null); + setWebEst(null); + setSecs(0); + } + }, "Discard")) + }, /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-body)', + margin: '0 0 8px' + } + }, "Beta's 80% window moves ", /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-data)' + } + }, "Mar 3\u201312 \u2192 Mar 5\u201314"), ". Capacity absorbs the rest."), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: 0 + } + }, "I added the docs ticket you mentioned and wired the dependency. Shall I make it so?")), /*#__PURE__*/React.createElement(Tray, { + editable: true + })) : null, stage === 'filed' ? /*#__PURE__*/React.createElement(Card, { + jade: true, + style: { + maxWidth: 560 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 10, + alignItems: 'flex-start' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + color: 'var(--ok)', + display: 'inline-flex', + alignItems: 'center', + gap: 8, + font: 'var(--text-title)' + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "circle-check", + size: 22 + }), " Filed"), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-body)', + margin: 0 + } + }, tickets.length, " issues opened in gitea with ", /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-data)' + } + }, "est/*"), " and ", /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-data)' + } + }, "p/*"), " labels \u2014 nothing else touched."), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: 0 + } + }, "Elapsed ", clock, " \u2014 under budget. No bot comments, no synthetic issues; your repo remains yours."), /*#__PURE__*/React.createElement(Button, { + iconRight: "arrow-right", + onClick: onDone + }, "To morning service"))) : null); +} +Object.assign(window, { + CaptureScreen +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/CaptureScreen.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/Chart.jsx +try { (() => { +// Burn-up chart with Monte Carlo forecast cone — geometry, not decoration. +function BurnUpCone({ + width = 640, + height = 220 +}) { + const pad = { + l: 34, + r: 96, + t: 16, + b: 26 + }; + const W = width - pad.l - pad.r; + const H = height - pad.t - pad.b; + const x = f => pad.l + f * W; + const y = f => pad.t + (1 - f) * H; + + // scope line (total work), actual completed, cone from today + const today = 0.58; + const actual = [[0, 0], [0.08, 0.05], [0.18, 0.13], [0.26, 0.16], [0.36, 0.27], [0.46, 0.38], [0.58, 0.47]]; + const coneHi = [[0.58, 0.47], [0.72, 0.66], [0.86, 0.88], [0.95, 1.0]]; + const coneLo = [[0.58, 0.47], [0.74, 0.58], [0.9, 0.74], [1.0, 0.86]]; + const mid = [[0.58, 0.47], [0.76, 0.63], [0.92, 0.83], [1.0, 0.93]]; + const pts = arr => arr.map(([a, b]) => `${x(a)},${y(b)}`).join(' '); + const cone = [...coneHi, ...[...coneLo].reverse()]; + return /*#__PURE__*/React.createElement("svg", { + width: "100%", + viewBox: `0 0 ${width} ${height}`, + style: { + display: 'block' + } + }, [0, 0.25, 0.5, 0.75, 1].map(f => /*#__PURE__*/React.createElement("line", { + key: f, + x1: pad.l, + x2: width - pad.r, + y1: y(f), + y2: y(f), + stroke: "var(--line-1)", + strokeWidth: "1" + })), /*#__PURE__*/React.createElement("line", { + x1: pad.l, + x2: width - pad.r, + y1: y(1), + y2: y(1), + stroke: "var(--line-2)", + strokeWidth: "1.5" + }), /*#__PURE__*/React.createElement("text", { + x: pad.l, + y: y(1) - 6, + style: { + font: '400 10.5px var(--font-mono)', + fill: 'var(--ink-3)' + } + }, "scope \xB7 42 issues"), /*#__PURE__*/React.createElement("polygon", { + points: pts(cone), + fill: "var(--cone-fill)" + }), /*#__PURE__*/React.createElement("polyline", { + points: pts(coneHi), + fill: "none", + stroke: "var(--cone-line)", + strokeWidth: "1.2", + strokeDasharray: "3 3" + }), /*#__PURE__*/React.createElement("polyline", { + points: pts(coneLo), + fill: "none", + stroke: "var(--cone-line)", + strokeWidth: "1.2", + strokeDasharray: "3 3" + }), /*#__PURE__*/React.createElement("polyline", { + points: pts(mid), + fill: "none", + stroke: "var(--cone-line)", + strokeWidth: "1.4" + }), /*#__PURE__*/React.createElement("polyline", { + points: pts(actual), + fill: "none", + stroke: "var(--cone-actual)", + strokeWidth: "2" + }), /*#__PURE__*/React.createElement("circle", { + cx: x(0.58), + cy: y(0.47), + r: "3.5", + fill: "var(--cone-actual)" + }), /*#__PURE__*/React.createElement("line", { + x1: x(today), + x2: x(today), + y1: pad.t, + y2: height - pad.b, + stroke: "var(--jade)", + strokeWidth: "1" + }), /*#__PURE__*/React.createElement("text", { + x: x(today) + 5, + y: pad.t + 10, + style: { + font: '400 10.5px var(--font-mono)', + fill: 'var(--jade-7)' + } + }, "today"), /*#__PURE__*/React.createElement("line", { + x1: x(0.95) + 4, + x2: x(0.95) + 4, + y1: y(1.0), + y2: y(0.86) + H * 0.14 * 0 + (y(0.86) - y(0.86)), + stroke: "none" + }), /*#__PURE__*/React.createElement("text", { + x: width - pad.r + 10, + y: y(0.95), + style: { + font: '500 11.5px var(--font-mono)', + fill: 'var(--ink-1)' + } + }, "80%"), /*#__PURE__*/React.createElement("text", { + x: width - pad.r + 10, + y: y(0.95) + 14, + style: { + font: '400 11px var(--font-mono)', + fill: 'var(--ink-2)' + } + }, "Mar 3\u201312"), /*#__PURE__*/React.createElement("text", { + x: pad.l, + y: height - 8, + style: { + font: '400 10.5px var(--font-mono)', + fill: 'var(--ink-3)' + } + }, "Jan 6"), /*#__PURE__*/React.createElement("text", { + x: width - pad.r - 34, + y: height - 8, + style: { + font: '400 10.5px var(--font-mono)', + fill: 'var(--ink-3)' + } + }, "Mar 15")); +} + +// Runway bar: milestone due date vs forecast range position +function RunwayBar({ + m +}) { + const toneColor = m.tone === 'warn' ? 'var(--warn)' : 'var(--ok)'; + const left = Math.max(0, (m.pos - m.spread / 2) * 100); + const w = Math.min(100 - left, m.spread * 100); + return /*#__PURE__*/React.createElement("div", { + style: { + position: 'relative', + height: 22, + background: 'var(--paper-2)', + borderRadius: 4, + overflow: 'hidden' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + position: 'absolute', + left: `${left}%`, + width: `${w}%`, + top: 0, + bottom: 0, + background: 'var(--cone-fill)', + borderLeft: `1.5px solid ${toneColor}`, + borderRight: `1.5px solid ${toneColor}` + } + }), /*#__PURE__*/React.createElement("div", { + style: { + position: 'absolute', + left: `${m.pos * 100}%`, + top: 0, + bottom: 0, + width: 2, + background: toneColor + } + }), /*#__PURE__*/React.createElement("div", { + style: { + position: 'absolute', + left: 'calc(88% - 1px)', + top: 0, + bottom: 0, + width: 2, + background: 'var(--ink-1)' + } + })); +} +Object.assign(window, { + BurnUpCone, + RunwayBar +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/Chart.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/ChatPanel.jsx +try { (() => { +// Agent panel — chat is the write-path +function ChatPanel({ + onOpenDirectives, + offline +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Icon, + IconButton + } = DS; + const d = window.CT_DATA; + const [msgs, setMsgs] = React.useState(d.chat); + const [text, setText] = React.useState(''); + const [thinking, setThinking] = React.useState(false); + const scrollRef = React.useRef(null); + React.useEffect(() => { + const el = scrollRef.current; + if (el) el.scrollTop = el.scrollHeight; + }, [msgs, thinking]); + const send = () => { + const t = text.trim(); + if (!t) return; + setMsgs(m => [...m, { + from: 'user', + text: t + }]); + setText(''); + setThinking(true); + setTimeout(() => { + setThinking(false); + setMsgs(m => [...m, { + from: 'agent', + text: d.cannedReply + }]); + }, 900); + }; + return /*#__PURE__*/React.createElement("aside", { + style: { + width: 330, + flexShrink: 0, + display: 'flex', + flexDirection: 'column', + background: 'var(--surface-card)', + borderLeft: '1px solid var(--line-1)', + minHeight: 0 + } + }, /*#__PURE__*/React.createElement("header", { + style: { + display: 'flex', + alignItems: 'center', + gap: 8, + padding: '14px 16px', + borderBottom: '1px solid var(--line-1)', + flexShrink: 0 + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "sparkles", + size: 16, + style: { + color: offline ? 'var(--ink-3)' : 'var(--jade)' + } + }), /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-body-strong)', + color: 'var(--ink-1)' + } + }, "Reginald"), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)', + marginLeft: 'auto' + } + }, offline ? 'offline · queueing' : 'gemma-4b · local'), /*#__PURE__*/React.createElement(IconButton, { + icon: "history", + label: "Directive log", + size: "sm", + onClick: onOpenDirectives + })), /*#__PURE__*/React.createElement("div", { + ref: scrollRef, + style: { + flex: 1, + overflowY: 'auto', + padding: 16, + display: 'flex', + flexDirection: 'column', + gap: 12, + minHeight: 0 + } + }, msgs.map((m, i) => m.from === 'agent' ? /*#__PURE__*/React.createElement("div", { + key: i, + style: { + font: 'var(--text-agent)', + color: 'var(--ink-1)', + lineHeight: 1.55 + } + }, m.text) : /*#__PURE__*/React.createElement("div", { + key: i, + style: { + alignSelf: 'flex-end', + maxWidth: '85%', + background: 'var(--paper-2)', + borderRadius: '10px 10px 2px 10px', + padding: '8px 12px', + font: 'var(--text-small)', + color: 'var(--ink-1)' + } + }, m.text)), offline ? /*#__PURE__*/React.createElement("div", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-3)' + } + }, "The model is away from its desk. Reads still work; writes will wait their turn.") : null, thinking ? /*#__PURE__*/React.createElement("div", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-3)' + } + }, "considering\u2026") : null), /*#__PURE__*/React.createElement("div", { + style: { + padding: 14, + borderTop: '1px solid var(--line-1)', + flexShrink: 0 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'flex-end', + gap: 8, + background: 'var(--paper-0)', + border: '1px solid var(--line-2)', + borderRadius: 'var(--radius-2)', + padding: '8px 8px 8px 12px' + } + }, /*#__PURE__*/React.createElement("textarea", { + value: text, + onChange: e => setText(e.target.value), + onKeyDown: e => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + send(); + } + }, + placeholder: offline ? 'Writes wait for the connection…' : 'Tell me what to do…', + disabled: offline, + rows: 1, + style: { + flex: 1, + resize: 'none', + border: 'none', + outline: 'none', + background: 'transparent', + font: 'var(--text-body)', + color: 'var(--ink-1)', + lineHeight: 1.45, + maxHeight: 96 + } + }), /*#__PURE__*/React.createElement("button", { + type: "button", + onClick: send, + "aria-label": "Send", + disabled: offline, + style: { + width: 30, + height: 30, + borderRadius: 'var(--radius-2)', + border: 'none', + cursor: offline ? 'not-allowed' : 'pointer', + background: offline ? 'var(--paper-3)' : 'var(--accent)', + color: offline ? 'var(--ink-3)' : 'var(--ink-inverse)', + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + flexShrink: 0 + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "send", + size: 14 + }))), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-caption)', + color: 'var(--ink-3)', + margin: '8px 2px 0' + } + }, "Chat is the write-path. Destructive changes are proposed, never assumed."))); +} +Object.assign(window, { + ChatPanel +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/ChatPanel.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/DepsGraph.jsx +try { (() => { +// Dependency graph drill-in — layered DAG, critical path in spruce +function DepsGraph({ + onOpenIssue +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Tag, + Icon + } = DS; + const g = window.CT_DATA.deps; + const PAD = 14, + COLW = 206, + ROWH = 106, + NW = 176, + NH = 84; + const X = c => PAD + c * COLW; + const Y = r => PAD + r * ROWH; + const maxCol = g.milestone.col; + const maxRow = Math.max(...g.nodes.map(n => n.row), g.milestone.row); + const W = PAD * 2 + maxCol * COLW + NW; + const H = PAD * 2 + maxRow * ROWH + NH; + const MSW = 158, + MSH = 44; + const pos = {}; + g.nodes.forEach(n => { + pos[n.id] = { + x: X(n.col), + y: Y(n.row), + w: NW, + h: NH + }; + }); + pos['ms'] = { + x: X(g.milestone.col), + y: Y(g.milestone.row) + (NH - MSH) / 2, + w: MSW, + h: MSH + }; + const edgePath = e => { + const a = pos[e.from], + b = pos[e.to]; + const x1 = a.x + a.w, + y1 = a.y + a.h / 2; + const x2 = b.x, + y2 = b.y + b.h / 2; + const dx = Math.max(28, (x2 - x1) / 2); + return `M ${x1} ${y1} C ${x1 + dx} ${y1}, ${x2 - dx} ${y2}, ${x2 - 5} ${y2}`; + }; + const STATES = { + done: { + icon: 'circle-check', + color: 'var(--ok)', + label: 'done' + }, + steeping: { + icon: 'clock', + color: 'var(--warn)', + label: 'steeping' + }, + review: { + icon: 'git-pull-request', + color: 'var(--info)', + label: 'in review' + }, + triage: { + icon: 'circle-dot', + color: 'var(--ink-3)', + label: 'triage' + }, + diagnosis: { + icon: 'circle-dashed', + color: 'var(--ink-3)', + label: 'diagnosis' + } + }; + const Node = ({ + n + }) => { + const st = STATES[n.state]; + const crit = g.critical.includes(n.id); + return /*#__PURE__*/React.createElement("div", { + onClick: () => onOpenIssue({ + id: n.id, + title: n.title, + labels: n.tags, + rationale: n.rationale, + days: n.state === 'steeping' ? n.days : undefined + }), + style: { + position: 'absolute', + left: pos[n.id].x, + top: pos[n.id].y, + width: NW, + height: NH, + background: n.state === 'done' ? 'var(--paper-2)' : 'var(--surface-card)', + border: `1px solid ${crit ? 'var(--spruce-5)' : 'var(--line-1)'}`, + boxShadow: crit ? 'var(--shadow-1), inset 2px 0 0 var(--accent)' : 'var(--shadow-1)', + borderRadius: 'var(--radius-2)', + padding: '9px 11px', + cursor: 'pointer', + display: 'flex', + flexDirection: 'column', + gap: 6, + opacity: n.state === 'done' ? 0.72 : 1, + transition: 'border-color var(--duration-fast) var(--ease-out)' + }, + onMouseEnter: e => { + e.currentTarget.style.borderColor = crit ? 'var(--accent)' : 'var(--line-2)'; + }, + onMouseLeave: e => { + e.currentTarget.style.borderColor = crit ? 'var(--spruce-5)' : 'var(--line-1)'; + } + }, /*#__PURE__*/React.createElement("div", { + style: { + font: '500 12px/1.3 var(--font-sans)', + color: 'var(--ink-1)', + display: '-webkit-box', + WebkitLineClamp: 2, + WebkitBoxOrient: 'vertical', + overflow: 'hidden' + } + }, n.title), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 6, + marginTop: 'auto' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)' + } + }, "#", n.id), /*#__PURE__*/React.createElement("span", { + style: { + display: 'inline-flex', + alignItems: 'center', + gap: 4, + font: '400 10.5px var(--font-mono)', + color: st.color, + whiteSpace: 'nowrap' + } + }, /*#__PURE__*/React.createElement(Icon, { + name: st.icon, + size: 11 + }), st.label, n.state === 'steeping' && n.days ? ` ${n.days}` : ''), n.tags.filter(t => t.startsWith('p/')).map(t => /*#__PURE__*/React.createElement("span", { + key: t, + style: { + font: '500 10.5px var(--font-mono)', + color: 'var(--ink-3)', + marginLeft: 'auto' + } + }, t)))); + }; + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 12, + flex: 1, + minHeight: 0 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 18, + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-2)', + whiteSpace: 'nowrap' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + display: 'inline-flex', + alignItems: 'center', + gap: 7 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 22, + height: 2, + background: 'var(--accent)', + display: 'inline-block' + } + }), "critical path"), /*#__PURE__*/React.createElement("span", { + style: { + display: 'inline-flex', + alignItems: 'center', + gap: 7 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 22, + height: 0, + borderTop: '1.5px solid var(--line-2)', + display: 'inline-block' + } + }), "blocks"), /*#__PURE__*/React.createElement("span", { + style: { + marginLeft: 'auto', + color: 'var(--ink-3)' + } + }, "unattached: ", g.unattached.map(i => `#${i}`).join(' · '))), /*#__PURE__*/React.createElement("div", { + style: { + overflow: 'auto', + flex: 1, + minHeight: 0, + border: '1px solid var(--line-1)', + borderRadius: 'var(--radius-3)', + background: 'var(--surface-app)' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + position: 'relative', + width: W, + height: H + } + }, /*#__PURE__*/React.createElement("svg", { + width: W, + height: H, + style: { + position: 'absolute', + inset: 0, + pointerEvents: 'none' + } + }, /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("marker", { + id: "dg-arrow", + viewBox: "0 0 8 8", + refX: "7", + refY: "4", + markerWidth: "7", + markerHeight: "7", + orient: "auto-start-reverse" + }, /*#__PURE__*/React.createElement("path", { + d: "M 0 0.5 L 7.5 4 L 0 7.5 z", + fill: "var(--line-2)" + })), /*#__PURE__*/React.createElement("marker", { + id: "dg-arrow-crit", + viewBox: "0 0 8 8", + refX: "7", + refY: "4", + markerWidth: "7", + markerHeight: "7", + orient: "auto-start-reverse" + }, /*#__PURE__*/React.createElement("path", { + d: "M 0 0.5 L 7.5 4 L 0 7.5 z", + fill: "var(--accent)" + }))), g.edges.map((e, i) => /*#__PURE__*/React.createElement("path", { + key: i, + d: edgePath(e), + fill: "none", + stroke: e.crit ? 'var(--accent)' : 'var(--line-2)', + strokeWidth: e.crit ? 2 : 1.5, + markerEnd: `url(#${e.crit ? 'dg-arrow-crit' : 'dg-arrow'})` + }))), g.nodes.map(n => /*#__PURE__*/React.createElement(Node, { + key: n.id, + n: n + })), /*#__PURE__*/React.createElement("div", { + style: { + position: 'absolute', + left: pos['ms'].x, + top: pos['ms'].y, + width: MSW, + height: MSH, + display: 'flex', + alignItems: 'center', + gap: 8, + padding: '0 14px', + background: 'var(--accent)', + color: 'var(--ink-inverse)', + borderRadius: 'var(--radius-round)', + boxShadow: 'var(--shadow-2)' + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "milestone", + size: 15 + }), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: '600 12.5px/1.2 var(--font-sans)' + } + }, g.milestone.name), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 10.5px/1.2 var(--font-mono)', + opacity: 0.8 + } + }, "due ", g.milestone.due))))), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: 0 + } + }, "Four issues sit on the critical path, and #87 is the cork in the bottle. Remove it and everything pours.")); +} +Object.assign(window, { + DepsGraph +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/DepsGraph.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/DirectivesScreen.jsx +try { (() => { +// Directive log — append-only ledger + the consequence diff (propose-approve) +function DirectivesScreen() { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Card, + Button, + Badge, + Icon + } = DS; + const d = window.CT_DATA.directives; + const [pending, setPending] = React.useState(d.pending); + const [entries, setEntries] = React.useState(d.entries); + const resolve = status => { + setEntries(e => [{ + seq: pending.seq, + who: pending.who, + when: pending.when, + what: pending.what, + why: 'pilot demo on the 14th', + status, + consequence: status === 'applied' ? '#78 +5d · Beta 80% Mar 5–14' : 'withdrawn before apply' + }, ...e]); + setPending(null); + }; + const toneColor = { + ok: 'var(--ok)', + warn: 'var(--warn)', + info: 'var(--info)', + danger: 'var(--danger)' + }; + const statusBadge = { + applied: { + tone: 'ok', + label: 'applied' + }, + withdrawn: { + tone: 'neutral', + label: 'withdrawn' + }, + superseded: { + tone: 'info', + label: 'superseded' + } + }; + return /*#__PURE__*/React.createElement("div", { + style: { + maxWidth: 760, + margin: '0 auto', + display: 'flex', + flexDirection: 'column', + gap: 16 + } + }, /*#__PURE__*/React.createElement("header", { + style: { + borderBottom: 'var(--rule-double)', + paddingBottom: 14 + } + }, /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-display)', + color: 'var(--ink-1)', + margin: 0 + } + }, "Directives"), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-data)', + color: 'var(--ink-3)', + margin: '6px 0 0' + } + }, "append-only \xB7 JSONL in pm-state \xB7 who, when, what, why")), pending ? /*#__PURE__*/React.createElement(Card, { + overline: `Awaiting your word · directive #00${pending.seq}`, + title: "Consequence diff", + jade: true, + footer: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, { + onClick: () => resolve('applied') + }, "Make it so"), /*#__PURE__*/React.createElement(Button, { + variant: "secondary", + onClick: () => {} + }, "Amend"), /*#__PURE__*/React.createElement(Button, { + variant: "ghost", + onClick: () => resolve('withdrawn') + }, "Withdraw")) + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 12 + } + }, /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-body)', + margin: 0, + color: 'var(--ink-2)' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-body-strong)', + color: 'var(--ink-1)' + } + }, pending.who), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-3)' + } + }, " \xB7 ", pending.when), /*#__PURE__*/React.createElement("br", null), "\u201C", pending.what, "\u201D"), /*#__PURE__*/React.createElement("div", { + style: { + borderTop: '1px solid var(--line-1)' + } + }, pending.diff.map(r => /*#__PURE__*/React.createElement("div", { + key: r.change, + style: { + display: 'flex', + alignItems: 'center', + gap: 10, + padding: '9px 0', + borderBottom: '1px solid var(--line-1)' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 7, + height: 7, + borderRadius: '50%', + background: toneColor[r.tone], + flexShrink: 0 + } + }), /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-small)', + color: 'var(--ink-1)', + flex: 1 + } + }, r.change), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 12px var(--font-mono)', + color: 'var(--ink-3)', + whiteSpace: 'nowrap' + } + }, r.from, " ", /*#__PURE__*/React.createElement("span", { + style: { + color: 'var(--ink-2)' + } + }, "\u2192"), " ", /*#__PURE__*/React.createElement("span", { + style: { + color: r.tone === 'warn' ? 'var(--warn)' : 'var(--ink-1)' + } + }, r.to))))), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: 0 + } + }, "Cheap, as consequences go. Shall I make it so?"))) : /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 8, + font: 'var(--text-small)', + color: 'var(--ink-3)', + padding: '2px 2px' + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "circle-check", + size: 14, + style: { + color: 'var(--ok)' + } + }), "Nothing awaits your word. Directives are given in chat; consequences appear here first."), /*#__PURE__*/React.createElement(Card, { + overline: "The ledger", + flush: true + }, /*#__PURE__*/React.createElement("div", null, entries.map((e, i) => { + const sb = statusBadge[e.status]; + return /*#__PURE__*/React.createElement("div", { + key: e.seq, + style: { + display: 'flex', + gap: 14, + padding: '14px 20px', + borderTop: i === 0 ? 'none' : '1px solid var(--line-1)' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: 6, + flexShrink: 0 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: '500 11px var(--font-mono)', + color: 'var(--ink-3)' + } + }, "#00", e.seq), /*#__PURE__*/React.createElement("span", { + style: { + width: 1, + flex: 1, + background: 'var(--line-1)' + } + })), /*#__PURE__*/React.createElement("div", { + style: { + flex: 1, + minWidth: 0, + display: 'flex', + flexDirection: 'column', + gap: 5 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 8, + flexWrap: 'wrap' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 20, + height: 20, + borderRadius: '50%', + background: 'var(--spruce-2)', + color: 'var(--accent-text)', + font: '600 8.5px/20px var(--font-sans)', + textAlign: 'center', + flexShrink: 0 + } + }, e.who.split(' ').map(w => w[0]).join('')), /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-body-strong)', + color: 'var(--ink-1)', + whiteSpace: 'nowrap' + } + }, e.who), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)', + whiteSpace: 'nowrap' + } + }, e.when), e.why ? /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)', + whiteSpace: 'nowrap' + } + }, "\xB7 why: ", e.why) : null, /*#__PURE__*/React.createElement("span", { + style: { + marginLeft: 'auto' + } + }, /*#__PURE__*/React.createElement(Badge, { + tone: sb.tone + }, sb.label))), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-body)', + color: 'var(--ink-1)', + margin: 0 + } + }, "\u201C", e.what, "\u201D"), /*#__PURE__*/React.createElement("p", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-2)', + margin: 0 + } + }, e.consequence))); + }))), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-caption)', + color: 'var(--ink-3)', + margin: 0 + } + }, "Entries are never edited. Corrections are new entries \u2014 the ledger remembers everything, politely.")); +} +Object.assign(window, { + DirectivesScreen +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/DirectivesScreen.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/FocusScreen.jsx +try { (() => { +// Morning service — focus screen +function FocusScreen({ + onOpenIssue +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Card, + Tag, + Badge, + Button, + IconButton + } = DS; + const d = window.CT_DATA; + const FocusRow = ({ + slot, + issue, + jade + }) => /*#__PURE__*/React.createElement(Card, { + overline: slot, + jade: jade, + title: /*#__PURE__*/React.createElement("a", { + href: "#", + onClick: e => { + e.preventDefault(); + onOpenIssue(issue); + }, + style: { + color: 'inherit', + border: 'none' + } + }, issue.title), + actions: /*#__PURE__*/React.createElement(IconButton, { + icon: "ellipsis", + label: "More", + size: "sm" + }), + footer: jade ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, { + size: "sm" + }, "Start"), /*#__PURE__*/React.createElement(Button, { + size: "sm", + variant: "ghost" + }, "Defer"), /*#__PURE__*/React.createElement("span", { + style: { + marginLeft: 'auto', + font: 'var(--text-caption)', + color: 'var(--ink-3)' + } + }, "scheduler pick \xB7 critical path")) : null + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 8, + marginBottom: 10, + flexWrap: 'wrap' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-data)', + color: 'var(--ink-3)' + } + }, "#", issue.id), issue.labels.map(l => /*#__PURE__*/React.createElement(Tag, { + key: l, + label: l + })), issue.steeping ? /*#__PURE__*/React.createElement(Badge, { + tone: "warn", + dot: true + }, "steeping ", issue.steeping) : null), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: 0 + } + }, issue.rationale)); + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 16 + } + }, /*#__PURE__*/React.createElement("header", { + style: { + borderBottom: 'var(--rule-double)', + paddingBottom: 14, + display: 'flex', + alignItems: 'baseline', + justifyContent: 'space-between', + gap: 12 + } + }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-display)', + color: 'var(--ink-1)', + margin: 0 + } + }, "Morning service"), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-data)', + color: 'var(--ink-3)', + margin: '6px 0 0', + whiteSpace: 'nowrap' + } + }, d.today, " \xB7 reconcile 3.2s")), /*#__PURE__*/React.createElement(Badge, { + tone: "ok", + dot: true + }, "ahead of forecast")), /*#__PURE__*/React.createElement("div", { + style: { + display: 'grid', + gridTemplateColumns: '1.4fr 1fr 1fr', + gap: 14, + alignItems: 'start' + } + }, /*#__PURE__*/React.createElement(FocusRow, { + slot: "Now", + issue: d.focus.now, + jade: true + }), /*#__PURE__*/React.createElement(FocusRow, { + slot: "Next", + issue: d.focus.next + }), /*#__PURE__*/React.createElement(FocusRow, { + slot: "Later", + issue: d.focus.later + })), /*#__PURE__*/React.createElement(Card, { + overline: "Milestone \xB7 Beta", + title: /*#__PURE__*/React.createElement(React.Fragment, null, "80% this lands ", /*#__PURE__*/React.createElement("span", { + style: { + whiteSpace: 'nowrap' + } + }, "Mar 3\u201312")), + actions: /*#__PURE__*/React.createElement(IconButton, { + icon: "chart-line", + label: "Open runway", + size: "sm" + }) + }, /*#__PURE__*/React.createElement(window.BurnUpCone, null), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: '10px 0 0' + } + }, "The cone has narrowed since Friday. I'm quietly pleased."))); +} +Object.assign(window, { + FocusScreen +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/FocusScreen.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/GanttView.jsx +try { (() => { +// Gantt drill-in — scheduler-derived bars, critical chain, 80% forecast tails +function GanttView({ + onOpenIssue +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Icon + } = DS; + const g = window.CT_DATA.gantt; + const LABELW = 232, + DAYW = 21, + ROWH = 36, + HEADH = 30; + const chartW = g.days * DAYW; + const W = LABELW + chartW; + const H = HEADH + g.rows.length * ROWH; + const X = d => LABELW + d * DAYW; + const BAR = { + done: { + bg: 'var(--paper-3)', + border: 'transparent', + text: 'var(--ink-3)' + }, + steeping: { + bg: 'var(--accent)', + border: 'transparent', + text: 'var(--ink-inverse)' + }, + review: { + bg: 'var(--info-tint)', + border: 'var(--info)', + text: 'var(--info)' + }, + scheduled: { + bg: 'var(--spruce-2)', + border: 'var(--spruce-3)', + text: 'var(--accent-text)' + } + }; + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 12, + flex: 1, + minHeight: 0 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 16, + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-2)', + whiteSpace: 'nowrap', + flexWrap: 'wrap' + } + }, [['steeping', 'in work'], ['review', 'in review'], ['scheduled', 'scheduled'], ['done', 'done']].map(([k, label]) => /*#__PURE__*/React.createElement("span", { + key: k, + style: { + display: 'inline-flex', + alignItems: 'center', + gap: 6 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 16, + height: 9, + borderRadius: 3, + background: BAR[k].bg, + border: `1px solid ${BAR[k].border}`, + display: 'inline-block' + } + }), label)), /*#__PURE__*/React.createElement("span", { + style: { + display: 'inline-flex', + alignItems: 'center', + gap: 6 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 18, + height: 0, + borderTop: '2px dotted var(--cone-line)', + display: 'inline-block' + } + }), "80% tail"), /*#__PURE__*/React.createElement("span", { + style: { + display: 'inline-flex', + alignItems: 'center', + gap: 6 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 2, + height: 12, + background: 'var(--jade)', + display: 'inline-block' + } + }), "today")), /*#__PURE__*/React.createElement("div", { + style: { + overflow: 'auto', + flex: 1, + minHeight: 0, + border: '1px solid var(--line-1)', + borderRadius: 'var(--radius-3)', + background: 'var(--surface-card)' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + position: 'relative', + width: W, + height: H + } + }, g.weeks.map(w => /*#__PURE__*/React.createElement(React.Fragment, { + key: w.at + }, /*#__PURE__*/React.createElement("div", { + style: { + position: 'absolute', + left: X(w.at), + top: HEADH, + bottom: 0, + width: 1, + background: 'var(--line-1)' + } + }), /*#__PURE__*/React.createElement("span", { + style: { + position: 'absolute', + left: X(w.at) + 5, + top: 8, + font: '400 10.5px var(--font-mono)', + color: 'var(--ink-3)', + whiteSpace: 'nowrap' + } + }, w.label))), /*#__PURE__*/React.createElement("div", { + style: { + position: 'absolute', + left: X(g.band.from), + width: (g.band.to - g.band.from) * DAYW, + top: HEADH, + bottom: 0, + background: 'var(--cone-fill)' + } + }), /*#__PURE__*/React.createElement("span", { + style: { + position: 'absolute', + left: X(g.band.from) + 5, + bottom: 6, + font: '500 10.5px var(--font-mono)', + color: 'var(--cone-line)', + whiteSpace: 'nowrap' + } + }, g.band.label), /*#__PURE__*/React.createElement("div", { + style: { + position: 'absolute', + left: X(g.due.at) - 1, + top: HEADH, + bottom: 0, + width: 2, + background: 'var(--ink-1)' + } + }), /*#__PURE__*/React.createElement("span", { + style: { + position: 'absolute', + left: X(g.due.at) - 4, + top: HEADH - 12, + width: 8, + height: 8, + background: 'var(--ink-1)', + transform: 'rotate(45deg)' + } + }), /*#__PURE__*/React.createElement("div", { + style: { + position: 'absolute', + left: X(g.today), + top: HEADH, + bottom: 0, + width: 2, + background: 'var(--jade)' + } + }), g.rows.map((r, i) => { + const top = HEADH + i * ROWH; + const st = BAR[r.state]; + return /*#__PURE__*/React.createElement(React.Fragment, { + key: r.id + }, /*#__PURE__*/React.createElement("div", { + style: { + position: 'absolute', + left: 0, + right: 0, + top: top, + height: 1, + background: 'var(--line-1)', + opacity: 0.6 + } + }), /*#__PURE__*/React.createElement("div", { + onClick: () => onOpenIssue({ + id: r.id, + title: r.title, + labels: [], + days: r.state === 'steeping' ? '4d' : undefined + }), + style: { + position: 'absolute', + left: 0, + width: LABELW, + height: ROWH, + top: top, + zIndex: 2, + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + gap: 8, + padding: '0 12px 0 14px', + background: 'var(--surface-card)', + borderRight: '1px solid var(--line-1)', + boxShadow: r.crit ? 'inset 2px 0 0 var(--accent)' : 'none' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)', + flexShrink: 0 + } + }, "#", r.id), /*#__PURE__*/React.createElement("span", { + style: { + font: '500 12px/1.3 var(--font-sans)', + color: 'var(--ink-1)', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap' + } + }, r.title), /*#__PURE__*/React.createElement("span", { + style: { + font: '600 8.5px/16px var(--font-sans)', + color: 'var(--accent-text)', + background: 'var(--spruce-2)', + width: 16, + height: 16, + borderRadius: '50%', + textAlign: 'center', + flexShrink: 0, + marginLeft: 'auto' + } + }, r.who)), /*#__PURE__*/React.createElement("div", { + style: { + position: 'absolute', + left: X(r.start), + width: (r.end - r.start) * DAYW, + top: top + 9, + height: 18, + background: st.bg, + border: `1px solid ${st.border}`, + borderRadius: 4, + boxShadow: r.crit && r.state !== 'steeping' ? 'inset 0 -2px 0 var(--accent)' : 'none' + } + }), r.p80 ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", { + style: { + position: 'absolute', + left: X(r.end), + width: (r.p80 - r.end) * DAYW, + top: top + 17, + height: 0, + borderTop: '2px dotted var(--cone-line)' + } + }), /*#__PURE__*/React.createElement("div", { + style: { + position: 'absolute', + left: X(r.p80) - 1, + top: top + 13, + width: 2, + height: 10, + background: 'var(--cone-line)' + } + })) : null); + }))), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: 0 + } + }, "The path holds if #87 lands by Wednesday. The dotted tails are your own history, wagging.")); +} +Object.assign(window, { + GanttView +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/GanttView.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/InboxScreen.jsx +try { (() => { +// Inbox — Reginald only rings the bell when it matters +function InboxScreen({ + onOpenIssue, + onOpenDirectives, + readIds, + setReadIds +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Card, + Tabs, + Button, + Icon + } = DS; + const all = window.CT_DATA.inbox; + const [tab, setTab] = React.useState('all'); + const isRead = n => !n.unread || readIds.includes(n.id); + const unreadCount = all.filter(n => !isRead(n)).length; + const FILTERS = { + all: () => true, + mentions: n => n.type === 'mention' || n.type === 'assignment', + drift: n => n.type === 'drift' || n.type === 'nag' || n.type === 'milestone', + system: n => n.type === 'system' || n.type === 'review' + }; + const items = all.filter(FILTERS[tab]); + const days = [...new Set(items.map(n => n.day))]; + const toneColor = { + ok: 'var(--ok)', + warn: 'var(--warn)', + info: 'var(--info)', + neutral: 'var(--ink-3)' + }; + const open = n => { + setReadIds(r => r.includes(n.id) ? r : [...r, n.id]); + if (n.issue) onOpenIssue(n.issue);else if (n.to === 'directives') onOpenDirectives(); + }; + return /*#__PURE__*/React.createElement("div", { + style: { + maxWidth: 760, + margin: '0 auto', + display: 'flex', + flexDirection: 'column', + gap: 14 + } + }, /*#__PURE__*/React.createElement("header", { + style: { + borderBottom: 'var(--rule-double)', + paddingBottom: 14, + display: 'flex', + alignItems: 'baseline', + justifyContent: 'space-between', + gap: 12 + } + }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-display)', + color: 'var(--ink-1)', + margin: 0 + } + }, "Inbox"), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-data)', + color: 'var(--ink-3)', + margin: '6px 0 0', + whiteSpace: 'nowrap' + } + }, unreadCount ? `${unreadCount} unread` : 'all read', " \xB7 nothing here rings twice")), unreadCount ? /*#__PURE__*/React.createElement(Button, { + variant: "ghost", + size: "sm", + onClick: () => setReadIds(all.map(n => n.id)) + }, "Mark all read") : null), /*#__PURE__*/React.createElement(Tabs, { + items: [{ + id: 'all', + label: 'All', + count: all.length + }, { + id: 'mentions', + label: 'Mentions', + icon: 'message-square' + }, { + id: 'drift', + label: 'Drift', + icon: 'chart-line' + }, { + id: 'system', + label: 'System', + icon: 'refresh-cw' + }], + active: tab, + onChange: setTab + }), /*#__PURE__*/React.createElement(Card, { + flush: true + }, /*#__PURE__*/React.createElement("div", null, days.map(day => /*#__PURE__*/React.createElement("div", { + key: day + }, /*#__PURE__*/React.createElement("div", { + style: { + font: 'var(--text-overline)', + letterSpacing: 'var(--letter-spacing-wide)', + textTransform: 'uppercase', + color: 'var(--ink-3)', + padding: '12px 20px 4px' + } + }, day), items.filter(n => n.day === day).map(n => { + const read = isRead(n); + return /*#__PURE__*/React.createElement("div", { + key: n.id, + onClick: () => open(n), + style: { + display: 'flex', + alignItems: 'flex-start', + gap: 12, + padding: '11px 20px', + cursor: n.issue || n.to ? 'pointer' : 'default', + opacity: read ? 0.72 : 1 + }, + onMouseEnter: e => { + e.currentTarget.style.background = 'var(--paper-2)'; + }, + onMouseLeave: e => { + e.currentTarget.style.background = 'transparent'; + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 6, + height: 6, + borderRadius: '50%', + background: read ? 'transparent' : 'var(--accent)', + flexShrink: 0, + marginTop: 7 + } + }), /*#__PURE__*/React.createElement("span", { + style: { + color: toneColor[n.tone], + display: 'inline-flex', + marginTop: 1, + flexShrink: 0 + } + }, /*#__PURE__*/React.createElement(Icon, { + name: n.icon, + size: 15 + })), /*#__PURE__*/React.createElement("div", { + style: { + flex: 1, + minWidth: 0 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + font: read ? 'var(--text-body)' : 'var(--text-body-strong)', + color: 'var(--ink-1)' + } + }, n.who ? /*#__PURE__*/React.createElement("span", { + style: { + fontWeight: 600 + } + }, n.who, " ") : null, n.text), /*#__PURE__*/React.createElement("div", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-2)', + marginTop: 2 + } + }, n.detail)), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)', + whiteSpace: 'nowrap', + marginTop: 2 + } + }, n.time)); + }))))), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: 0 + } + }, "I only ring the bell when it matters. The rest can wait for morning service.")); +} +Object.assign(window, { + InboxScreen +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/InboxScreen.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/IssueScreen.jsx +try { (() => { +// Issue detail — human intent (gitea) on the left, machine-derived (pm-state) on the right +function IssueScreen({ + issue, + onBack, + onOpenIssue +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Card, + Tag, + Badge, + Button, + Icon + } = DS; + const det = window.CT_DATA.issueDetail[issue.id] || { + state: 'triage', + assignee: 'Stephen', + milestone: 'Beta', + body: '', + comments: [], + lifecycle: [{ + stage: 'Diagnosis', + event: 'issue opened', + when: 'Feb 4 · 10:20', + icon: 'circle-dot', + done: true + }, { + stage: 'Triage', + event: 'labeled · milestoned Beta', + when: 'Feb 5 · 09:12', + icon: 'tag', + done: true + }, { + stage: 'Work start', + event: 'first branch or commit ref', + when: 'pending', + icon: 'git-commit-horizontal', + done: false + }, { + stage: 'Deploy', + event: 'PR merged', + when: 'pending', + icon: 'git-merge', + done: false + }, { + stage: 'Complete', + event: 'issue closed', + when: 'pending', + icon: 'circle-check', + done: false + }], + forecast: { + p80: 'starts wk of Feb 16', + note: 'queue position from scheduler' + }, + blocks: [], + blockedBy: [], + note: null + }; + const stateBadge = { + steeping: { + tone: 'warn', + label: `steeping${issue.days || issue.steeping ? ' ' + (issue.days || issue.steeping) : ''}` + }, + triage: { + tone: 'neutral', + label: 'triage' + }, + review: { + tone: 'info', + label: 'in review' + }, + done: { + tone: 'ok', + label: 'done' + } + }[det.state] || { + tone: 'neutral', + label: det.state + }; + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 16 + } + }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", { + type: "button", + onClick: onBack, + style: { + display: 'inline-flex', + alignItems: 'center', + gap: 6, + background: 'none', + border: 'none', + font: '500 12.5px var(--font-sans)', + color: 'var(--ink-2)', + cursor: 'pointer', + padding: '2px 0', + marginBottom: 10 + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "arrow-left", + size: 14 + }), " Back"), /*#__PURE__*/React.createElement("header", { + style: { + borderBottom: 'var(--rule-double)', + paddingBottom: 14, + display: 'flex', + alignItems: 'flex-start', + gap: 16 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + flex: 1, + minWidth: 0 + } + }, /*#__PURE__*/React.createElement("p", { + style: { + font: '400 13px var(--font-mono)', + color: 'var(--ink-3)', + margin: '0 0 6px' + } + }, "#", issue.id, " \xB7 stephen/commitea"), /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-title)', + color: 'var(--ink-1)', + margin: 0 + } + }, issue.title), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 8, + marginTop: 10, + flexWrap: 'wrap' + } + }, /*#__PURE__*/React.createElement(Badge, { + tone: stateBadge.tone, + dot: true + }, stateBadge.label), (issue.labels || []).map(l => /*#__PURE__*/React.createElement(Tag, { + key: l, + label: l + })), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-3)', + display: 'inline-flex', + alignItems: 'center', + gap: 5 + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "milestone", + size: 12 + }), det.milestone), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-3)', + display: 'inline-flex', + alignItems: 'center', + gap: 5 + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "user", + size: 12 + }), det.assignee))), /*#__PURE__*/React.createElement(Button, { + variant: "secondary", + icon: "arrow-up-right" + }, "Open in Gitea"))), /*#__PURE__*/React.createElement("div", { + style: { + display: 'grid', + gridTemplateColumns: '1fr 300px', + gap: 16, + alignItems: 'start' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 16 + } + }, /*#__PURE__*/React.createElement(Card, { + overline: "Description" + }, det.body ? /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-body)', + color: 'var(--ink-1)', + margin: 0, + lineHeight: 1.6 + } + }, det.body) : /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-3)', + margin: 0 + } + }, "No description was written. I have opinions about that, but I'll keep them warm.")), /*#__PURE__*/React.createElement(Card, { + overline: `Comments · ${det.comments.length}`, + flush: true + }, /*#__PURE__*/React.createElement("div", null, det.comments.map((c, i) => /*#__PURE__*/React.createElement("div", { + key: i, + style: { + display: 'flex', + gap: 12, + padding: '14px 20px', + borderBottom: '1px solid var(--line-1)' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 24, + height: 24, + borderRadius: '50%', + background: 'var(--spruce-2)', + color: 'var(--accent-text)', + font: '600 9px/24px var(--font-sans)', + textAlign: 'center', + flexShrink: 0 + } + }, c.who.split(' ').map(w => w[0]).join('')), /*#__PURE__*/React.createElement("div", { + style: { + flex: 1, + minWidth: 0 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'baseline', + gap: 8 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-body-strong)', + color: 'var(--ink-1)', + whiteSpace: 'nowrap' + } + }, c.who), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)', + whiteSpace: 'nowrap' + } + }, c.when)), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-body)', + color: 'var(--ink-1)', + margin: '4px 0 0' + } + }, c.text)))), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: 8, + padding: '14px 20px', + alignItems: 'flex-end' + } + }, /*#__PURE__*/React.createElement("textarea", { + placeholder: "Comment \u2014 this writes to gitea, as you", + rows: 2, + style: { + flex: 1, + resize: 'none', + font: 'var(--text-body)', + color: 'var(--ink-1)', + lineHeight: 1.5, + background: 'var(--paper-0)', + border: '1px solid var(--line-2)', + borderRadius: 'var(--radius-2)', + padding: '8px 11px', + outline: 'none' + } + }), /*#__PURE__*/React.createElement(Button, { + size: "sm", + variant: "secondary" + }, "Comment")))), det.note ? /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: 0, + padding: '0 2px' + } + }, det.note) : null), /*#__PURE__*/React.createElement(Card, { + overline: "Machine-derived", + flush: true + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + padding: '14px 20px', + display: 'flex', + flexDirection: 'column', + gap: 0 + } + }, det.lifecycle.map((s, i) => /*#__PURE__*/React.createElement("div", { + key: s.stage, + style: { + display: 'flex', + gap: 10 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + display: 'inline-flex', + color: s.done ? 'var(--ok)' : 'var(--ink-3)', + padding: '2px 0' + } + }, /*#__PURE__*/React.createElement(Icon, { + name: s.icon, + size: 14 + })), i < det.lifecycle.length - 1 ? /*#__PURE__*/React.createElement("span", { + style: { + width: 1, + flex: 1, + minHeight: 14, + background: s.done ? 'var(--spruce-3)' : 'var(--line-1)' + } + }) : null), /*#__PURE__*/React.createElement("div", { + style: { + paddingBottom: i < det.lifecycle.length - 1 ? 12 : 0 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + font: `500 12px var(--font-sans)`, + color: s.done ? 'var(--ink-1)' : 'var(--ink-3)' + } + }, s.stage), /*#__PURE__*/React.createElement("div", { + style: { + font: '400 10.5px var(--font-mono)', + color: 'var(--ink-3)', + marginTop: 2, + whiteSpace: 'nowrap' + } + }, s.event), /*#__PURE__*/React.createElement("div", { + style: { + font: '400 10.5px var(--font-mono)', + color: s.done ? 'var(--ink-2)' : 'var(--ink-3)', + whiteSpace: 'nowrap' + } + }, s.when))))), /*#__PURE__*/React.createElement("div", { + style: { + padding: '12px 20px', + borderTop: '1px solid var(--line-1)' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + font: 'var(--text-overline)', + letterSpacing: 'var(--letter-spacing-wide)', + textTransform: 'uppercase', + color: 'var(--ink-3)', + marginBottom: 6 + } + }, "Forecast"), /*#__PURE__*/React.createElement("div", { + style: { + font: '500 13px var(--font-mono)', + color: 'var(--ink-1)' + } + }, "80% ", det.forecast.p80), /*#__PURE__*/React.createElement("div", { + style: { + font: '400 10.5px var(--font-mono)', + color: 'var(--ink-3)', + marginTop: 3 + } + }, det.forecast.note)), /*#__PURE__*/React.createElement("div", { + style: { + padding: '12px 20px', + borderTop: '1px solid var(--line-1)' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + font: 'var(--text-overline)', + letterSpacing: 'var(--letter-spacing-wide)', + textTransform: 'uppercase', + color: 'var(--ink-3)', + marginBottom: 6 + } + }, "Dependencies"), det.blocks.length === 0 && det.blockedBy.length === 0 ? /*#__PURE__*/React.createElement("div", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-3)' + } + }, "none") : /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 6 + } + }, det.blocks.length ? /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 6, + flexWrap: 'wrap' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)' + } + }, "blocks"), det.blocks.map(b => /*#__PURE__*/React.createElement("button", { + key: b, + type: "button", + onClick: () => onOpenIssue({ + id: b, + title: b === 91 ? 'Webhook listener: reconcile on reconnect' : 'Monte Carlo engine: percentile bands', + labels: b === 91 ? ['est/3d', 'p/2'] : ['est/5d', 'p/1'] + }), + style: { + font: '500 11px var(--font-mono)', + color: 'var(--accent-text)', + background: 'var(--spruce-1)', + border: '1px solid var(--spruce-2)', + borderRadius: 'var(--radius-1)', + padding: '2px 7px', + cursor: 'pointer' + } + }, "#", b))) : null, det.blockedBy.length ? /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 6 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)' + } + }, "blocked by"), det.blockedBy.map(b => /*#__PURE__*/React.createElement("span", { + key: b, + style: { + font: '500 11px var(--font-mono)', + color: 'var(--ink-2)' + } + }, "#", b))) : null)), /*#__PURE__*/React.createElement("div", { + style: { + padding: '10px 20px 14px', + borderTop: '1px solid var(--line-1)' + } + }, /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-caption)', + color: 'var(--ink-3)', + margin: 0 + } + }, "Lives in pm-state. Your repo never sees any of it.")))))); +} +Object.assign(window, { + IssueScreen +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/IssueScreen.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/MilestoneScreen.jsx +try { (() => { +// Milestone detail — scope, cone, issues; forecasts stay ranges +function MilestoneScreen({ + onBack, + onOpenIssue +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Card, + Tag, + Badge, + Button, + Icon + } = DS; + const cols = window.CT_DATA.columns; + const byState = ids => cols.flatMap(c => c.issues.map(i => ({ + ...i, + col: c.label + }))).filter(i => ids.includes(i.id)); + const groups = [{ + label: 'Steeping', + issues: byState([87, 84]) + }, { + label: 'In review', + issues: byState([92]) + }, { + label: 'Queued', + issues: byState([102, 103, 99, 96, 78]) + }, { + label: 'Done', + issues: byState([71, 69, 65]), + muted: true + }]; + const Stat = ({ + label, + value, + tone + }) => /*#__PURE__*/React.createElement("div", { + style: { + flex: 1, + padding: '12px 18px', + borderRight: '1px solid var(--line-1)' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + font: 'var(--text-overline)', + letterSpacing: 'var(--letter-spacing-wide)', + textTransform: 'uppercase', + color: 'var(--ink-3)', + marginBottom: 5 + } + }, label), /*#__PURE__*/React.createElement("div", { + style: { + font: `500 14px var(--font-mono)`, + color: tone || 'var(--ink-1)', + whiteSpace: 'nowrap' + } + }, value)); + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 16 + } + }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", { + type: "button", + onClick: onBack, + style: { + display: 'inline-flex', + alignItems: 'center', + gap: 6, + background: 'none', + border: 'none', + font: '500 12.5px var(--font-sans)', + color: 'var(--ink-2)', + cursor: 'pointer', + padding: '2px 0', + marginBottom: 10 + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "arrow-left", + size: 14 + }), " Runway"), /*#__PURE__*/React.createElement("header", { + style: { + borderBottom: 'var(--rule-double)', + paddingBottom: 14, + display: 'flex', + alignItems: 'flex-start', + gap: 16 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + flex: 1, + minWidth: 0 + } + }, /*#__PURE__*/React.createElement("p", { + style: { + font: '400 12px var(--font-mono)', + color: 'var(--ink-3)', + margin: '0 0 6px', + display: 'inline-flex', + alignItems: 'center', + gap: 6 + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "milestone", + size: 13 + }), " milestone \xB7 due Mar 15 \xB7 soft \u2014 scope may flex"), /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-display)', + color: 'var(--ink-1)', + margin: 0 + } + }, "Beta"), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 8, + marginTop: 10 + } + }, /*#__PURE__*/React.createElement(Badge, { + tone: "ok", + dot: true + }, "ahead of forecast"), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 12px var(--font-mono)', + color: 'var(--ink-2)', + whiteSpace: 'nowrap' + } + }, "80% Mar 3\u201312"))), /*#__PURE__*/React.createElement(Button, { + variant: "secondary", + icon: "arrow-up-right" + }, "Open in Gitea"))), /*#__PURE__*/React.createElement(Card, { + flush: true + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex' + } + }, /*#__PURE__*/React.createElement(Stat, { + label: "Scope", + value: "42 issues \xB7 est 61d" + }), /*#__PURE__*/React.createElement(Stat, { + label: "Done", + value: "24 \xB7 57%" + }), /*#__PURE__*/React.createElement(Stat, { + label: "Forecast", + value: "80% Mar 3\u201312" + }), /*#__PURE__*/React.createElement("div", { + style: { + flex: 1, + padding: '12px 18px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + font: 'var(--text-overline)', + letterSpacing: 'var(--letter-spacing-wide)', + textTransform: 'uppercase', + color: 'var(--ink-3)', + marginBottom: 5 + } + }, "Drift \xB7 7d"), /*#__PURE__*/React.createElement("div", { + style: { + font: '500 14px var(--font-mono)', + color: 'var(--ok)', + whiteSpace: 'nowrap' + } + }, "\u22122d \xB7 cone narrowed")))), /*#__PURE__*/React.createElement("div", { + style: { + display: 'grid', + gridTemplateColumns: '1.2fr 1fr', + gap: 14, + alignItems: 'start' + } + }, /*#__PURE__*/React.createElement(Card, { + overline: "Burn-up", + title: /*#__PURE__*/React.createElement(React.Fragment, null, "80% this lands ", /*#__PURE__*/React.createElement("span", { + style: { + whiteSpace: 'nowrap' + } + }, "Mar 3\u201312")), + jade: true + }, /*#__PURE__*/React.createElement(window.BurnUpCone, null), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: '10px 0 0' + } + }, "Comfortably ahead. Beta needs #87 more than it needs my commentary.")), /*#__PURE__*/React.createElement(Card, { + overline: "Issues", + flush: true + }, /*#__PURE__*/React.createElement("div", null, groups.map(g => /*#__PURE__*/React.createElement("div", { + key: g.label + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 7, + padding: '10px 20px 6px' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-overline)', + letterSpacing: 'var(--letter-spacing-wide)', + textTransform: 'uppercase', + color: 'var(--ink-3)' + } + }, g.label), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)' + } + }, g.issues.length)), g.issues.map(i => /*#__PURE__*/React.createElement("div", { + key: i.id, + onClick: () => onOpenIssue({ + id: i.id, + title: i.title, + labels: i.labels, + days: i.days + }), + style: { + display: 'flex', + alignItems: 'center', + gap: 8, + padding: '7px 20px', + cursor: 'pointer', + opacity: g.muted ? 0.6 : 1 + }, + onMouseEnter: e => { + e.currentTarget.style.background = 'var(--paper-2)'; + }, + onMouseLeave: e => { + e.currentTarget.style.background = 'transparent'; + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)', + width: 34, + flexShrink: 0 + } + }, "#", i.id), /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-small)', + color: 'var(--ink-1)', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + flex: 1 + } + }, i.title), (i.labels || []).filter(l => l.startsWith('est/')).map(l => /*#__PURE__*/React.createElement(Tag, { + key: l, + label: l + })))))))))); +} +Object.assign(window, { + MilestoneScreen +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/MilestoneScreen.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/OnboardingScreen.jsx +try { (() => { +// Onboarding / first connect — welcome → connect gitea → choose repo → bootstrap +function OnboardingScreen({ + onDone +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Button, + Input, + Radio, + Tag, + Badge, + Icon + } = DS; + const [step, setStep] = React.useState(0); + const [conn, setConn] = React.useState('idle'); // idle | testing | ok + const [repo, setRepo] = React.useState('stephen/commitea'); + const [boot, setBoot] = React.useState(-1); // -1 idle, 0..2 running, 3 done + + React.useEffect(() => { + if (conn !== 'testing') return; + const t = setTimeout(() => setConn('ok'), 1100); + return () => clearTimeout(t); + }, [conn]); + React.useEffect(() => { + if (boot < 0 || boot >= 3) return; + const t = setTimeout(() => setBoot(boot + 1), 700); + return () => clearTimeout(t); + }, [boot]); + const STEPS = ['Welcome', 'Connect', 'Repo', 'Bootstrap']; + const BOOT_TASKS = ['Create stephen/pm-state (the sidecar)', 'Apply the label schema to stephen/commitea', 'Install a webhook · endpoint :48731']; + const Frame = ({ + children, + footer + }) => /*#__PURE__*/React.createElement("div", { + style: { + background: 'var(--surface-card)', + border: '1px solid var(--line-1)', + borderRadius: 'var(--radius-3)', + boxShadow: 'var(--shadow-jade-line), var(--shadow-2)', + padding: '30px 36px', + display: 'flex', + flexDirection: 'column', + gap: 16, + width: '100%' + } + }, children, footer ? /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: 8, + justifyContent: 'flex-end', + borderTop: '1px solid var(--line-1)', + paddingTop: 16 + } + }, footer) : null); + return /*#__PURE__*/React.createElement("div", { + style: { + minHeight: '100vh', + background: 'var(--surface-app)', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + padding: 32, + gap: 22 + }, + "data-screen-label": "onboarding" + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 12 + } + }, /*#__PURE__*/React.createElement("img", { + src: "../../assets/logo-icon.png", + width: "34", + height: "34", + alt: "", + style: { + borderRadius: 8, + display: 'block' + } + }), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 27px/1 var(--font-serif-display)', + color: 'var(--ink-1)' + } + }, "Commi", /*#__PURE__*/React.createElement("span", { + style: { + color: 'var(--accent-text)' + } + }, "Tea"))), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 14 + } + }, STEPS.map((s, i) => /*#__PURE__*/React.createElement("div", { + key: s, + style: { + display: 'flex', + alignItems: 'center', + gap: 14 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + display: 'inline-flex', + alignItems: 'center', + gap: 7 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 20, + height: 20, + borderRadius: '50%', + textAlign: 'center', + font: '500 10.5px/20px var(--font-mono)', + background: i < step ? 'var(--accent)' : i === step ? 'var(--spruce-2)' : 'var(--paper-2)', + color: i < step ? 'var(--ink-inverse)' : i === step ? 'var(--accent-text)' : 'var(--ink-3)' + } + }, i < step ? '\u2713' : i + 1), /*#__PURE__*/React.createElement("span", { + style: { + font: `${i === step ? 600 : 400} 12px/1 var(--font-sans)`, + color: i === step ? 'var(--ink-1)' : 'var(--ink-3)' + } + }, s)), i < STEPS.length - 1 ? /*#__PURE__*/React.createElement("span", { + style: { + width: 24, + height: 1, + background: 'var(--line-2)' + } + }) : null))), /*#__PURE__*/React.createElement("div", { + style: { + width: 'min(540px, 100%)' + } + }, step === 0 ? /*#__PURE__*/React.createElement(Frame, { + footer: /*#__PURE__*/React.createElement(Button, { + iconRight: "arrow-right", + onClick: () => setStep(1) + }, "Begin") + }, /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-title)', + color: 'var(--ink-1)', + margin: 0 + } + }, "Good morning."), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent-lg)', + color: 'var(--ink-2)', + margin: 0 + } + }, "I'm Reginald, your project manager. I interview you instead of making you fill in forms, I forecast in honest ranges, and I never do the arithmetic myself \u2014 there's a scheduler for that."), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-body)', + color: 'var(--ink-2)', + margin: 0 + } + }, "Your plans live in your own Gitea as ordinary issues and labels. Delete me and nothing human is lost.")) : null, step === 1 ? /*#__PURE__*/React.createElement(Frame, { + footer: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, { + variant: "ghost", + onClick: () => setStep(0) + }, "Back"), /*#__PURE__*/React.createElement(Button, { + iconRight: "arrow-right", + disabled: conn !== 'ok', + onClick: () => setStep(2) + }, "Continue")) + }, /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-title)', + color: 'var(--ink-1)', + margin: 0 + } + }, "Your Gitea"), /*#__PURE__*/React.createElement(Input, { + label: "Base URL", + icon: "link", + mono: true, + defaultValue: "https://gitea.stephenmann.io" + }), /*#__PURE__*/React.createElement(Input, { + label: "Access token", + icon: "keyboard", + mono: true, + type: "password", + defaultValue: "ct_9f2e81c4a7d6", + hint: "Scopes: repo, issue. Nothing more." + }), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 12 + } + }, /*#__PURE__*/React.createElement(Button, { + variant: "secondary", + icon: conn === 'testing' ? 'loader-circle' : 'zap', + disabled: conn === 'testing', + onClick: () => setConn('testing') + }, conn === 'testing' ? 'Ringing the bell…' : 'Test connection'), conn === 'ok' ? /*#__PURE__*/React.createElement(Badge, { + tone: "ok", + dot: true + }, "connected \xB7 3 repos visible") : null)) : null, step === 2 ? /*#__PURE__*/React.createElement(Frame, { + footer: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, { + variant: "ghost", + onClick: () => setStep(1) + }, "Back"), /*#__PURE__*/React.createElement(Button, { + iconRight: "arrow-right", + onClick: () => setStep(3) + }, "Continue")) + }, /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-title)', + color: 'var(--ink-1)', + margin: 0 + } + }, "Which repo shall I manage?"), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 8 + } + }, ['stephen/commitea', 'stephen/novelpad', 'stephen/infra'].map(r => /*#__PURE__*/React.createElement("label", { + key: r, + style: { + display: 'flex', + alignItems: 'center', + gap: 10, + padding: '10px 14px', + cursor: 'pointer', + background: repo === r ? 'var(--spruce-1)' : 'var(--paper-0)', + border: `1px solid ${repo === r ? 'var(--spruce-3)' : 'var(--line-1)'}`, + borderRadius: 'var(--radius-2)' + } + }, /*#__PURE__*/React.createElement(Radio, { + name: "repo", + checked: repo === r, + onChange: () => setRepo(r) + }), /*#__PURE__*/React.createElement(Icon, { + name: "git-branch", + size: 14, + style: { + color: 'var(--ink-3)' + } + }), /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-data)', + color: 'var(--ink-1)' + } + }, r)))), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-caption)', + color: 'var(--ink-3)', + margin: 0 + } + }, "One to start. You can add more later in Settings.")) : null, step === 3 ? /*#__PURE__*/React.createElement(Frame, { + footer: boot === 3 ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, { + variant: "ghost", + onClick: () => onDone('focus') + }, "Just look around"), /*#__PURE__*/React.createElement(Button, { + icon: "sparkles", + onClick: () => onDone('capture') + }, "Start a capture")) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, { + variant: "ghost", + onClick: () => setStep(2), + disabled: boot >= 0 + }, "Back"), /*#__PURE__*/React.createElement(Button, { + disabled: boot >= 0, + onClick: () => setBoot(0) + }, "Make it so")) + }, /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-title)', + color: 'var(--ink-1)', + margin: 0 + } + }, boot === 3 ? 'All set.' : 'With your approval'), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 10 + } + }, BOOT_TASKS.map((t, i) => /*#__PURE__*/React.createElement("div", { + key: t, + style: { + display: 'flex', + alignItems: 'center', + gap: 10 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + display: 'inline-flex', + color: boot > i ? 'var(--ok)' : boot === i ? 'var(--warn)' : 'var(--ink-3)' + } + }, /*#__PURE__*/React.createElement(Icon, { + name: boot > i ? 'circle-check' : boot === i ? 'loader-circle' : 'circle-dashed', + size: 15 + })), /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-body)', + color: boot > i ? 'var(--ink-1)' : 'var(--ink-2)', + whiteSpace: 'nowrap' + } + }, t))), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexWrap: 'wrap', + gap: 5, + paddingLeft: 25 + } + }, ['est/1d', 'est/2d', 'est/3d', 'est/5d', 'est/8d', 'p/1', 'p/2', 'p/3', 'p/4', 'deadline/hard'].map(l => /*#__PURE__*/React.createElement(Tag, { + key: l, + label: l + })))), boot === 3 ? /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: 0 + } + }, "The pot is empty. Tell me what you're planning and I'll draw up the tickets.") : /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-caption)', + color: 'var(--ink-3)', + margin: 0 + } + }, "No bot comments, no body frontmatter, no synthetic issues \u2014 ever. Labels are the only footprint.")) : null), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)' + } + }, "first run \xB7 everything reversible")); +} +Object.assign(window, { + OnboardingScreen +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/OnboardingScreen.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/RunwayScreen.jsx +try { (() => { +// Runway — capacity vs milestone dates; ranges, never points +function RunwayScreen({ + onOpenCalibration, + onOpenMilestone +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Card, + Badge, + Tag, + Icon + } = DS; + const d = window.CT_DATA; + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 16 + } + }, /*#__PURE__*/React.createElement("header", { + style: { + borderBottom: 'var(--rule-double)', + paddingBottom: 14 + } + }, /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-display)', + color: 'var(--ink-1)', + margin: 0 + } + }, "Runway"), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-data)', + color: 'var(--ink-3)', + margin: '6px 0 0' + } + }, "capacity vs milestone dates \xB7 calibrated on 27 closed issues")), /*#__PURE__*/React.createElement(Card, { + overline: "Milestones", + flush: true + }, /*#__PURE__*/React.createElement("div", null, d.runway.map((m, i) => /*#__PURE__*/React.createElement("div", { + key: m.name, + onClick: onOpenMilestone, + style: { + display: 'grid', + gridTemplateColumns: '160px 1fr 150px 90px', + gap: 16, + alignItems: 'center', + cursor: 'pointer', + padding: '14px 20px', + borderTop: i === 0 ? 'none' : '1px solid var(--line-1)' + }, + onMouseEnter: e => { + e.currentTarget.style.background = 'var(--paper-2)'; + }, + onMouseLeave: e => { + e.currentTarget.style.background = 'transparent'; + } + }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", { + style: { + font: 'var(--text-body-strong)', + color: 'var(--ink-1)', + display: 'flex', + alignItems: 'center', + gap: 7 + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "milestone", + size: 14, + style: { + color: 'var(--ink-3)' + } + }), m.name), /*#__PURE__*/React.createElement("div", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-3)', + marginTop: 3 + } + }, "due ", m.due, m.hard ? ' ' : ''), m.hard ? /*#__PURE__*/React.createElement(Tag, { + label: "deadline/hard", + style: { + marginTop: 5 + } + }) : null), /*#__PURE__*/React.createElement(window.RunwayBar, { + m: m + }), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 12px var(--font-mono)', + color: 'var(--ink-2)' + } + }, "80% ", m.p80), /*#__PURE__*/React.createElement(Badge, { + tone: m.tone, + dot: true + }, m.note))))), /*#__PURE__*/React.createElement("div", { + style: { + display: 'grid', + gridTemplateColumns: '1fr 1fr', + gap: 14, + alignItems: 'start' + } + }, /*#__PURE__*/React.createElement(Card, { + overline: "Capacity", + flush: true + }, /*#__PURE__*/React.createElement("div", null, d.capacity.map((p, i) => /*#__PURE__*/React.createElement("div", { + key: p.who, + style: { + display: 'flex', + alignItems: 'center', + gap: 12, + padding: '12px 20px', + borderTop: i === 0 ? 'none' : '1px solid var(--line-1)' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 26, + height: 26, + borderRadius: '50%', + background: 'var(--spruce-2)', + color: 'var(--accent-text)', + font: '600 10px/26px var(--font-sans)', + textAlign: 'center', + flexShrink: 0 + } + }, p.who.split(' ').map(w => w[0]).join('')), /*#__PURE__*/React.createElement("div", { + style: { + flex: 1, + minWidth: 0 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + font: 'var(--text-body-strong)' + } + }, p.who), /*#__PURE__*/React.createElement("div", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-3)', + marginTop: 2 + } + }, p.slices)), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 12px var(--font-mono)', + color: 'var(--ink-2)' + } + }, p.hours))))), /*#__PURE__*/React.createElement(Card, { + overline: "Calibration", + actions: /*#__PURE__*/React.createElement(DS.IconButton, { + icon: "arrow-up-right", + label: "Full report", + size: "sm", + onClick: onOpenCalibration + }) + }, /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-body)', + margin: '0 0 8px' + } + }, "Your estimates run ", /*#__PURE__*/React.createElement("strong", null, "18% optimistic"), " on ", /*#__PURE__*/React.createElement("code", null, "est/3d"), " and above. Smaller tickets are honest."), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: 0 + } + }, "I widen the cone accordingly. No judgement \u2014 it's the most common shape of hope.")))); +} +Object.assign(window, { + RunwayScreen +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/RunwayScreen.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/SettingsScreen.jsx +try { (() => { +// Settings — gitea connection, sync, model roles, labels, rituals, appearance +function SettingsScreen({ + dark, + setDark +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Card, + Input, + Select, + Switch, + Radio, + Button, + IconButton, + Tag, + Badge, + Icon + } = DS; + const [webhooks, setWebhooks] = React.useState(true); + const [reconcile, setReconcile] = React.useState(true); + const [poll, setPoll] = React.useState(true); + const [nag, setNag] = React.useState(true); + const Row = ({ + children, + style + }) => /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 12, + ...style + } + }, children); + const Note = ({ + children + }) => /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-caption)', + color: 'var(--ink-3)', + margin: 0 + } + }, children); + return /*#__PURE__*/React.createElement("div", { + style: { + maxWidth: 720, + margin: '0 auto', + display: 'flex', + flexDirection: 'column', + gap: 16 + } + }, /*#__PURE__*/React.createElement("header", { + style: { + borderBottom: 'var(--rule-double)', + paddingBottom: 14 + } + }, /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-display)', + color: 'var(--ink-1)', + margin: 0 + } + }, "Settings"), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-data)', + color: 'var(--ink-3)', + margin: '6px 0 0' + } + }, "config lives in pm-state \xB7 versioned, portable")), /*#__PURE__*/React.createElement(Card, { + overline: "Gitea", + title: "Connection" + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 14 + } + }, /*#__PURE__*/React.createElement(Input, { + label: "Base URL", + icon: "link", + mono: true, + defaultValue: "https://gitea.stephenmann.io" + }), /*#__PURE__*/React.createElement(Input, { + label: "Access token", + icon: "keyboard", + mono: true, + type: "password", + defaultValue: "ct_9f2e81c4a7d6", + hint: "Scopes: repo, issue. Nothing more." + }), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 8 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: '600 13px/1.2 var(--font-sans)', + color: 'var(--ink-1)' + } + }, "Managed repos"), /*#__PURE__*/React.createElement(Row, { + style: { + padding: '8px 12px', + background: 'var(--paper-0)', + border: '1px solid var(--line-1)', + borderRadius: 'var(--radius-2)' + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "git-branch", + size: 14, + style: { + color: 'var(--ink-3)' + } + }), /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-data)', + color: 'var(--ink-1)', + flex: 1 + } + }, "stephen/commitea"), /*#__PURE__*/React.createElement(Badge, { + tone: "ok", + dot: true + }, "syncing"), /*#__PURE__*/React.createElement(IconButton, { + icon: "x", + label: "Stop managing", + size: "sm" + })), /*#__PURE__*/React.createElement(Row, { + style: { + padding: '8px 12px', + background: 'var(--paper-0)', + border: '1px solid var(--line-1)', + borderRadius: 'var(--radius-2)' + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "layers", + size: 14, + style: { + color: 'var(--ink-3)' + } + }), /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-data)', + color: 'var(--ink-1)', + flex: 1 + } + }, "stephen/pm-state"), /*#__PURE__*/React.createElement(Badge, null, "sidecar")), /*#__PURE__*/React.createElement(Note, null, "The sidecar holds machine-derived state only. Delete it and resync \u2014 no truth is lost."), /*#__PURE__*/React.createElement(Button, { + variant: "secondary", + size: "sm", + icon: "plus", + style: { + alignSelf: 'flex-start' + } + }, "Add repo")))), /*#__PURE__*/React.createElement(Card, { + overline: "Sync", + title: "Staying current" + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 12 + } + }, /*#__PURE__*/React.createElement(Row, null, /*#__PURE__*/React.createElement(Switch, { + label: "Webhooks while running", + checked: webhooks, + onChange: e => setWebhooks(e.target.checked) + }), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-3)', + marginLeft: 'auto' + } + }, "endpoint :48731 \xB7 healthy")), /*#__PURE__*/React.createElement(Switch, { + label: "Full reconcile on launch", + checked: reconcile, + onChange: e => setReconcile(e.target.checked) + }), /*#__PURE__*/React.createElement(Row, null, /*#__PURE__*/React.createElement(Switch, { + label: "Poll fallback", + checked: poll, + onChange: e => setPoll(e.target.checked) + }), /*#__PURE__*/React.createElement("div", { + style: { + marginLeft: 'auto', + width: 140 + } + }, /*#__PURE__*/React.createElement(Select, { + options: [{ + value: '2', + label: 'every 2 min' + }, { + value: '5', + label: 'every 5 min' + }, { + value: '15', + label: 'every 15 min' + }], + defaultValue: "5" + }))), /*#__PURE__*/React.createElement(Note, null, "last reconcile 3.2s \xB7 500 issues \xB7 nothing lost"))), /*#__PURE__*/React.createElement(Card, { + overline: "Models", + title: "The router" + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 14 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'grid', + gridTemplateColumns: '1fr 1fr', + gap: '12px 14px' + } + }, /*#__PURE__*/React.createElement(Select, { + label: "Prose & rituals", + options: [{ + value: 'gemma-4b', + label: 'gemma-4b · local' + }, { + value: 'qwen-7b', + label: 'qwen-7b · local' + }], + defaultValue: "gemma-4b" + }), /*#__PURE__*/React.createElement(Input, { + label: "Base URL", + mono: true, + defaultValue: "http://localhost:1234/v1" + }), /*#__PURE__*/React.createElement(Select, { + label: "Decomposition & negotiation", + options: [{ + value: 'qwen-72b', + label: 'qwen-72b · lm-studio box' + }, { + value: 'gpt-4o', + label: 'gpt-4o · OpenAI API' + }], + defaultValue: "qwen-72b" + }), /*#__PURE__*/React.createElement(Input, { + label: "Base URL", + mono: true, + defaultValue: "http://10.0.0.42:1234/v1" + })), /*#__PURE__*/React.createElement(Row, null, /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-3)' + } + }, "hot memory \u2264 2k tokens \xB7 math is never delegated to either")), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: 0 + } + }, "The small one writes my standup; the large one argues with your estimates. Neither is allowed near the arithmetic."))), /*#__PURE__*/React.createElement(Card, { + overline: "Labels", + title: "Schema" + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 10 + } + }, /*#__PURE__*/React.createElement(Row, { + style: { + flexWrap: 'wrap', + gap: 6 + } + }, ['est/1d', 'est/2d', 'est/3d', 'est/5d', 'est/8d'].map(l => /*#__PURE__*/React.createElement(Tag, { + key: l, + label: l + }))), /*#__PURE__*/React.createElement(Row, { + style: { + flexWrap: 'wrap', + gap: 6 + } + }, ['p/1', 'p/2', 'p/3', 'p/4'].map(l => /*#__PURE__*/React.createElement(Tag, { + key: l, + label: l + })), /*#__PURE__*/React.createElement(Tag, { + label: "deadline/hard" + })), /*#__PURE__*/React.createElement(Note, null, "Fixed sets, human-meaningful, visible in gitea. Not configurable \u2014 that is rather the point."))), /*#__PURE__*/React.createElement(Card, { + overline: "Rituals", + title: "Reginald's calendar" + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 12 + } + }, /*#__PURE__*/React.createElement(Row, null, /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-body)', + color: 'var(--ink-1)' + } + }, "Morning standup"), /*#__PURE__*/React.createElement("div", { + style: { + marginLeft: 'auto', + width: 120 + } + }, /*#__PURE__*/React.createElement(Select, { + options: [{ + value: '0630', + label: '06:30' + }, { + value: '0700', + label: '07:00' + }, { + value: '0800', + label: '08:00' + }], + defaultValue: "0700" + }))), /*#__PURE__*/React.createElement(Row, null, /*#__PURE__*/React.createElement(Switch, { + label: "Stale-blocker nagging", + checked: nag, + onChange: e => setNag(e.target.checked) + }), /*#__PURE__*/React.createElement("div", { + style: { + marginLeft: 'auto', + width: 140 + } + }, /*#__PURE__*/React.createElement(Select, { + options: [{ + value: '2', + label: 'after 2 days' + }, { + value: '3', + label: 'after 3 days' + }, { + value: '5', + label: 'after 5 days' + }], + defaultValue: "3" + }))))), /*#__PURE__*/React.createElement(Card, { + overline: "Appearance", + title: "Service" + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: 20 + } + }, /*#__PURE__*/React.createElement(Radio, { + name: "theme", + label: "Morning (light)", + checked: !dark, + onChange: () => setDark(false) + }), /*#__PURE__*/React.createElement(Radio, { + name: "theme", + label: "Evening (dark)", + checked: dark, + onChange: () => setDark(true) + }))), /*#__PURE__*/React.createElement(Card, null, /*#__PURE__*/React.createElement(Row, null, /*#__PURE__*/React.createElement("div", { + style: { + flex: 1 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + font: 'var(--text-body-strong)', + color: 'var(--ink-1)' + } + }, "Forget this gitea"), /*#__PURE__*/React.createElement(Note, null, "Removes the connection and the local cache. Gitea itself is untouched.")), /*#__PURE__*/React.createElement(Button, { + variant: "danger" + }, "Forget")))); +} +Object.assign(window, { + SettingsScreen +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/SettingsScreen.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/Shell.jsx +try { (() => { +// App shell — left rail + content + agent panel, issue page, theme toggle +function Shell() { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Icon, + Switch + } = DS; + const [view, setView] = React.useState('focus'); + const [prevView, setPrevView] = React.useState('focus'); + const [dark, setDark] = React.useState(false); + const [issue, setIssue] = React.useState(null); + const [offline, setOffline] = React.useState(false); + const [readIds, setReadIds] = React.useState([]); + const inboxUnread = window.CT_DATA.inbox.filter(n => n.unread && !readIds.includes(n.id)).length; + const openIssue = i => { + setIssue(i); + if (view !== 'issue') setPrevView(view); + setView('issue'); + }; + React.useEffect(() => { + document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light'); + }, [dark]); + if (view === 'firstrun') { + return /*#__PURE__*/React.createElement(window.OnboardingScreen, { + onDone: dest => setView(dest) + }); + } + const NAV = [{ + id: 'standup', + label: 'Standup', + icon: 'sun' + }, { + id: 'focus', + label: 'Morning service', + icon: 'coffee' + }, { + id: 'inbox', + label: 'Inbox', + icon: 'bell', + count: inboxUnread || null + }, { + id: 'capture', + label: 'Capture', + icon: 'plus' + }, { + id: 'board', + label: 'The pot', + icon: 'square-kanban' + }, { + id: 'runway', + label: 'Runway', + icon: 'chart-line' + }, { + id: 'directives', + label: 'Directives', + icon: 'flag' + }]; + const NavItem = ({ + item + }) => { + const active = view === item.id || view === 'issue' && prevView === item.id || (view === 'calibration' || view === 'milestone') && item.id === 'runway'; + return /*#__PURE__*/React.createElement("button", { + type: "button", + onClick: () => setView(item.id), + style: { + display: 'flex', + alignItems: 'center', + gap: 10, + width: '100%', + font: `${active ? 600 : 500} 13.5px/1 var(--font-sans)`, + color: active ? 'var(--ink-1)' : 'var(--ink-2)', + background: active ? 'var(--paper-2)' : 'transparent', + border: 'none', + borderRadius: 'var(--radius-2)', + padding: '9px 12px', + cursor: 'pointer', + textAlign: 'left', + transition: 'background var(--duration-fast) var(--ease-out)' + } + }, /*#__PURE__*/React.createElement(Icon, { + name: item.icon, + size: 16, + style: { + color: active ? 'var(--accent-text)' : 'var(--ink-3)' + } + }), item.label, item.count ? /*#__PURE__*/React.createElement("span", { + style: { + marginLeft: 'auto', + font: '500 10.5px/16px var(--font-mono)', + color: 'var(--accent-text)', + background: 'var(--spruce-2)', + borderRadius: 'var(--radius-round)', + padding: '0 6px' + } + }, item.count) : null); + }; + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + height: '100vh', + minWidth: 1280, + background: 'var(--surface-app)', + overflow: 'hidden' + }, + "data-screen-label": `app-${view}` + }, /*#__PURE__*/React.createElement("nav", { + style: { + width: 208, + flexShrink: 0, + display: 'flex', + flexDirection: 'column', + gap: 4, + padding: '18px 12px 14px', + borderRight: '1px solid var(--line-1)' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 9, + padding: '0 12px 16px' + } + }, /*#__PURE__*/React.createElement("img", { + src: "../../assets/logo-icon.png", + width: "24", + height: "24", + alt: "", + style: { + borderRadius: 6, + display: 'block' + } + }), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 21px/1 var(--font-serif-display)', + color: 'var(--ink-1)' + } + }, "Commi", /*#__PURE__*/React.createElement("span", { + style: { + color: 'var(--accent-text)' + } + }, "Tea"))), NAV.map(n => /*#__PURE__*/React.createElement(NavItem, { + key: n.id, + item: n + })), /*#__PURE__*/React.createElement("div", { + style: { + borderTop: '1px solid var(--line-1)', + margin: '10px 8px' + } + }), /*#__PURE__*/React.createElement(NavItem, { + item: { + id: 'settings', + label: 'Settings', + icon: 'settings-2' + } + }), /*#__PURE__*/React.createElement(NavItem, { + item: { + id: 'firstrun', + label: 'First run', + icon: 'play' + } + }), /*#__PURE__*/React.createElement(NavItem, { + item: { + id: 'states', + label: 'States', + icon: 'circle-dashed' + } + }), /*#__PURE__*/React.createElement("div", { + style: { + marginTop: 'auto', + padding: '0 12px', + display: 'flex', + flexDirection: 'column', + gap: 12 + } + }, /*#__PURE__*/React.createElement("button", { + type: "button", + onClick: () => setOffline(!offline), + title: "Toggle the connection (demo)", + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)', + display: 'flex', + alignItems: 'center', + gap: 6, + background: 'none', + border: 'none', + padding: 0, + cursor: 'pointer', + textAlign: 'left' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 6, + height: 6, + borderRadius: '50%', + background: offline ? 'var(--danger)' : 'var(--ok)', + display: 'inline-block' + } + }), "gitea.stephenmann.io"), /*#__PURE__*/React.createElement(Switch, { + label: /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-caption)' + } + }, "Evening service"), + checked: dark, + onChange: e => setDark(e.target.checked) + }))), /*#__PURE__*/React.createElement("main", { + style: { + flex: 1, + minWidth: 0, + overflowY: 'auto', + padding: '24px 28px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + maxWidth: 1120, + margin: '0 auto', + height: view === 'board' ? '100%' : 'auto', + display: 'flex', + flexDirection: 'column', + gap: 14 + } + }, offline ? /*#__PURE__*/React.createElement(window.OfflineBanner, null) : null, view === 'focus' ? /*#__PURE__*/React.createElement(window.FocusScreen, { + onOpenIssue: openIssue + }) : null, view === 'board' ? /*#__PURE__*/React.createElement(window.BoardScreen, { + onOpenIssue: openIssue + }) : null, view === 'runway' ? /*#__PURE__*/React.createElement(window.RunwayScreen, { + onOpenCalibration: () => setView('calibration'), + onOpenMilestone: () => setView('milestone') + }) : null, view === 'capture' ? /*#__PURE__*/React.createElement(window.CaptureScreen, { + onDone: () => setView('focus') + }) : null, view === 'standup' ? /*#__PURE__*/React.createElement(window.StandupScreen, { + onBegin: () => setView('focus'), + onOpenIssue: openIssue + }) : null, view === 'settings' ? /*#__PURE__*/React.createElement(window.SettingsScreen, { + dark: dark, + setDark: setDark + }) : null, view === 'directives' ? /*#__PURE__*/React.createElement(window.DirectivesScreen, null) : null, view === 'issue' && issue ? /*#__PURE__*/React.createElement(window.IssueScreen, { + issue: issue, + onBack: () => setView(prevView), + onOpenIssue: openIssue + }) : null, view === 'calibration' ? /*#__PURE__*/React.createElement(window.CalibrationScreen, { + onBack: () => setView('runway') + }) : null, view === 'milestone' ? /*#__PURE__*/React.createElement(window.MilestoneScreen, { + onBack: () => setView('runway'), + onOpenIssue: openIssue + }) : null, view === 'states' ? /*#__PURE__*/React.createElement(window.StatesScreen, { + onCapture: () => setView('capture') + }) : null, view === 'inbox' ? /*#__PURE__*/React.createElement(window.InboxScreen, { + onOpenIssue: openIssue, + onOpenDirectives: () => setView('directives'), + readIds: readIds, + setReadIds: setReadIds + }) : null)), /*#__PURE__*/React.createElement(window.ChatPanel, { + onOpenDirectives: () => setView('directives'), + offline: offline + })); +} +Object.assign(window, { + Shell +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/Shell.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/StandupScreen.jsx +try { (() => { +// Morning standup ritual — a typeset letter from Reginald: drift, plan, nag +const standupCSS = ` +@keyframes ct-standup-settle { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: none; } +} +.ct-standup-section { opacity: 1; } +@media (prefers-reduced-motion: no-preference) { + .ct-standup-section { animation: ct-standup-settle 320ms var(--ease-out) both; } +} +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-standup-css')) { + const s = document.createElement('style'); + s.id = 'ct-standup-css'; + s.textContent = standupCSS; + document.head.appendChild(s); + } +})(); +function StandupScreen({ + onBegin, + onOpenIssue +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Button, + Tag, + Badge, + Icon + } = DS; + const s = window.CT_DATA.standup; + const Section = ({ + overline, + children, + order + }) => /*#__PURE__*/React.createElement("section", { + className: "ct-standup-section", + style: { + animationDelay: `${order * 90}ms`, + display: 'flex', + flexDirection: 'column', + gap: 12 + } + }, /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-overline)', + letterSpacing: 'var(--letter-spacing-wide)', + textTransform: 'uppercase', + color: 'var(--ink-3)', + margin: 0, + borderTop: '1px solid var(--line-1)', + paddingTop: 14 + } + }, overline), children); + const toneColor = { + ok: 'var(--ok)', + warn: 'var(--warn)', + danger: 'var(--danger)' + }; + return /*#__PURE__*/React.createElement("div", { + style: { + maxWidth: 660, + margin: '0 auto' + } + }, /*#__PURE__*/React.createElement("article", { + style: { + background: 'var(--surface-card)', + border: '1px solid var(--line-1)', + borderRadius: 'var(--radius-3)', + boxShadow: 'var(--shadow-jade-line), var(--shadow-1)', + padding: '36px 44px 32px', + display: 'flex', + flexDirection: 'column', + gap: 20 + } + }, /*#__PURE__*/React.createElement("header", { + className: "ct-standup-section", + style: { + borderBottom: 'var(--rule-double)', + paddingBottom: 16 + } + }, /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-data)', + color: 'var(--ink-3)', + margin: '0 0 8px' + } + }, s.date, " \xB7 prepared 07:00"), /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-display)', + color: 'var(--ink-1)', + margin: 0 + } + }, "Morning standup")), /*#__PURE__*/React.createElement(Section, { + overline: "Overnight drift", + order: 1 + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 10 + } + }, s.drift.map(d => /*#__PURE__*/React.createElement("div", { + key: d.text, + style: { + display: 'flex', + alignItems: 'baseline', + gap: 10 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 7, + height: 7, + borderRadius: '50%', + background: toneColor[d.tone], + flexShrink: 0, + position: 'relative', + top: -1 + } + }), /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-body)', + color: 'var(--ink-1)', + flex: 1 + } + }, d.text), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-3)', + whiteSpace: 'nowrap' + } + }, d.delta))))), /*#__PURE__*/React.createElement(Section, { + overline: "Today's plan", + order: 2 + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 12 + } + }, s.plan.map(p => /*#__PURE__*/React.createElement("div", { + key: p.who, + style: { + display: 'flex', + gap: 12, + alignItems: 'flex-start' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 26, + height: 26, + borderRadius: '50%', + background: 'var(--spruce-2)', + color: 'var(--accent-text)', + font: '600 10px/26px var(--font-sans)', + textAlign: 'center', + flexShrink: 0 + } + }, p.who.split(' ').map(w => w[0]).join('')), /*#__PURE__*/React.createElement("div", { + style: { + flex: 1, + minWidth: 0 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 8, + flexWrap: 'wrap' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-body-strong)', + color: 'var(--ink-1)', + whiteSpace: 'nowrap' + } + }, p.who), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 12px var(--font-mono)', + color: 'var(--ink-2)' + } + }, p.pick), /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-small)', + color: 'var(--ink-2)' + } + }, p.title)), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: '4px 0 0' + } + }, p.why)))))), /*#__PURE__*/React.createElement(Section, { + overline: "Stale blockers", + order: 3 + }, /*#__PURE__*/React.createElement("div", { + onClick: () => onOpenIssue({ + id: s.nag.id, + title: 'Fix lifecycle inference on merge events', + labels: ['est/2d', 'p/1'], + days: s.nag.days + }), + style: { + display: 'flex', + gap: 10, + alignItems: 'flex-start', + cursor: 'pointer', + background: 'var(--warn-tint)', + borderRadius: 'var(--radius-2)', + padding: '12px 14px' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + color: 'var(--warn)', + display: 'inline-flex', + marginTop: 2 + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "clock", + size: 15 + })), /*#__PURE__*/React.createElement("div", { + style: { + flex: 1 + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 8, + flexWrap: 'wrap' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: '500 12.5px var(--font-mono)', + color: 'var(--ink-1)' + } + }, "#", s.nag.id), /*#__PURE__*/React.createElement(Badge, { + tone: "warn", + dot: true + }, "steeping ", s.nag.days), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-3)' + } + }, "blocks ", s.nag.blocks.join(', '))), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: '5px 0 0' + } + }, s.nag.text)))), /*#__PURE__*/React.createElement("footer", { + className: "ct-standup-section", + style: { + animationDelay: '360ms', + borderTop: '1px solid var(--line-1)', + paddingTop: 16, + display: 'flex', + alignItems: 'center', + gap: 10 + } + }, /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent-lg)', + color: 'var(--ink-1)', + margin: 0, + flex: 1 + } + }, "The kettle's on. \u2014 R."), /*#__PURE__*/React.createElement(Button, { + variant: "ghost", + onClick: onBegin + }, "Ask about the drift"), /*#__PURE__*/React.createElement(Button, { + iconRight: "arrow-right", + onClick: onBegin + }, "Begin the day")))); +} +Object.assign(window, { + StandupScreen +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/StandupScreen.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/StatesGallery.jsx +try { (() => { +// Shared empty/trouble states + the States gallery screen +function EmptyState({ + icon, + title, + line, + action, + onAction, + compact +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Button, + Icon + } = DS; + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + textAlign: 'center', + gap: 10, + padding: compact ? '28px 20px' : '52px 24px' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + width: 44, + height: 44, + borderRadius: '50%', + background: 'var(--paper-2)', + color: 'var(--ink-3)', + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center' + } + }, /*#__PURE__*/React.createElement(Icon, { + name: icon, + size: 19 + })), /*#__PURE__*/React.createElement("div", { + style: { + font: '400 20px/1.25 var(--font-serif-display)', + color: 'var(--ink-1)' + } + }, title), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: 0, + maxWidth: 380 + } + }, line), action ? /*#__PURE__*/React.createElement(Button, { + style: { + marginTop: 6 + }, + onClick: onAction + }, action) : null); +} +function OfflineBanner({ + retryIn +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Icon + } = DS; + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 10, + background: 'var(--warn-tint)', + border: '1px solid var(--warn)', + borderRadius: 'var(--radius-2)', + padding: '9px 14px' + } + }, /*#__PURE__*/React.createElement("span", { + style: { + color: 'var(--warn)', + display: 'inline-flex' + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "triangle-alert", + size: 15 + })), /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-small)', + color: 'var(--ink-1)', + flex: 1 + } + }, "Gitea isn't answering. I'll keep trying and say nothing more about it."), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-2)', + whiteSpace: 'nowrap' + } + }, "retry in ", retryIn || '0:12', " \xB7 reads from cache")); +} +function ModelAwayState() { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Icon, + Badge + } = DS; + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 8, + padding: '14px 16px' + } + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 8 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + color: 'var(--ink-3)', + display: 'inline-flex' + } + }, /*#__PURE__*/React.createElement(Icon, { + name: "sparkles", + size: 15 + })), /*#__PURE__*/React.createElement("span", { + style: { + font: 'var(--text-body-strong)', + color: 'var(--ink-2)' + } + }, "Reginald"), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)' + } + }, "model offline"), /*#__PURE__*/React.createElement("span", { + style: { + marginLeft: 'auto' + } + }, /*#__PURE__*/React.createElement(Badge, null, "queued: 1 directive"))), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-agent)', + color: 'var(--ink-2)', + margin: 0 + } + }, "The model is away from its desk. Reads still work; writes will wait their turn.")); +} +function StatesScreen({ + onCapture +}) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { + Button, + Badge, + Icon + } = DS; + const Specimen = ({ + label, + children + }) => /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 8 + } + }, /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11px var(--font-mono)', + color: 'var(--ink-3)' + } + }, label), /*#__PURE__*/React.createElement("div", { + style: { + border: '1px dashed var(--line-2)', + borderRadius: 'var(--radius-3)', + background: 'var(--surface-card)', + overflow: 'hidden' + } + }, children)); + return /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 16 + } + }, /*#__PURE__*/React.createElement("header", { + style: { + borderBottom: 'var(--rule-double)', + paddingBottom: 14 + } + }, /*#__PURE__*/React.createElement("h1", { + style: { + font: 'var(--text-display)', + color: 'var(--ink-1)', + margin: 0 + } + }, "States"), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-data)', + color: 'var(--ink-3)', + margin: '6px 0 0' + } + }, "empty & trouble \xB7 specimens as wired in the app")), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-overline)', + letterSpacing: 'var(--letter-spacing-wide)', + textTransform: 'uppercase', + color: 'var(--ink-3)', + margin: '4px 0 0' + } + }, "Empty"), /*#__PURE__*/React.createElement("div", { + style: { + display: 'grid', + gridTemplateColumns: '1fr 1fr', + gap: 14 + } + }, /*#__PURE__*/React.createElement(Specimen, { + label: "the pot \xB7 no issues" + }, /*#__PURE__*/React.createElement(EmptyState, { + compact: true, + icon: "inbox", + title: "The pot is empty", + line: "Tell me what you're planning and I'll draw up the tickets.", + action: "Start a capture", + onAction: onCapture + })), /*#__PURE__*/React.createElement(Specimen, { + label: "morning service \xB7 nothing scheduled" + }, /*#__PURE__*/React.createElement(EmptyState, { + compact: true, + icon: "coffee", + title: "Nothing to pour", + line: "Capture some work, or enjoy the silence \u2014 it never lasts." + })), /*#__PURE__*/React.createElement(Specimen, { + label: "directives \xB7 no entries" + }, /*#__PURE__*/React.createElement(EmptyState, { + compact: true, + icon: "flag", + title: "No directives yet", + line: "When you overrule the scheduler, it goes on the record here \u2014 who, when, what, why." + })), /*#__PURE__*/React.createElement(Specimen, { + label: "board search \xB7 no match" + }, /*#__PURE__*/React.createElement(EmptyState, { + compact: true, + icon: "search", + title: "Nothing by that name", + line: "The pot holds 24 issues; none of them answer to that." + }))), /*#__PURE__*/React.createElement("p", { + style: { + font: 'var(--text-overline)', + letterSpacing: 'var(--letter-spacing-wide)', + textTransform: 'uppercase', + color: 'var(--ink-3)', + margin: '8px 0 0' + } + }, "Trouble"), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + gap: 14 + } + }, /*#__PURE__*/React.createElement(Specimen, { + label: "gitea unreachable \xB7 banner above main content (toggle the rail's connection dot to see it live)" + }, /*#__PURE__*/React.createElement("div", { + style: { + padding: 12 + } + }, /*#__PURE__*/React.createElement(OfflineBanner, null))), /*#__PURE__*/React.createElement("div", { + style: { + display: 'grid', + gridTemplateColumns: '1fr 1fr', + gap: 14 + } + }, /*#__PURE__*/React.createElement(Specimen, { + label: "reginald's panel \xB7 model offline" + }, /*#__PURE__*/React.createElement(ModelAwayState, null)), /*#__PURE__*/React.createElement(Specimen, { + label: "webhooks down \xB7 poll fallback" + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + alignItems: 'center', + gap: 10, + padding: '14px 16px' + } + }, /*#__PURE__*/React.createElement(Badge, { + tone: "warn", + dot: true + }, "webhooks down"), /*#__PURE__*/React.createElement("span", { + style: { + font: '400 11.5px var(--font-mono)', + color: 'var(--ink-2)' + } + }, "polling every 2 min \xB7 updates may lag")))), /*#__PURE__*/React.createElement(Specimen, { + label: "first reconcile failed \xB7 full-screen" + }, /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + padding: '8px 0 20px' + } + }, /*#__PURE__*/React.createElement(EmptyState, { + compact: true, + icon: "refresh-cw", + title: "The reconcile failed", + line: "Gitea answered, then hung up mid-sentence. Your cache is intact; nothing human is lost." + }), /*#__PURE__*/React.createElement("div", { + style: { + display: 'flex', + gap: 8, + marginTop: -6 + } + }, /*#__PURE__*/React.createElement(Button, null, "Try again"), /*#__PURE__*/React.createElement(Button, { + variant: "ghost" + }, "Work from cache")))))); +} +Object.assign(window, { + EmptyState, + OfflineBanner, + ModelAwayState, + StatesScreen +}); +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/StatesGallery.jsx", error: String((e && e.message) || e) }); } + +// ui_kits/app/data.js +try { (() => { +// CommiTea fixture data — human-authored intent (gitea) + machine-derived state (sidecar) +window.CT_DATA = { + today: 'Tuesday 7 July 2026', + milestone: { + name: 'Beta', + due: '2026-03-15', + hard: false, + forecast: { + p80: 'Mar 3–12', + p50: 'Mar 5–8', + drift: '+0d' + } + }, + focus: { + now: { + id: 87, + title: 'Fix lifecycle inference on merge events', + labels: ['est/2d', 'p/1'], + steeping: '4d', + rationale: 'It blocks #91 and #92. I\u2019d take it first \u2014 the critical path agrees with me.' + }, + next: { + id: 91, + title: 'Webhook listener: reconcile on reconnect', + labels: ['est/3d', 'p/2'], + rationale: 'Ready the moment #87 lands. The estimate is yours; history says add a day.' + }, + later: { + id: 78, + title: 'Calibration store cold-start distributions', + labels: ['est/5d', 'p/3'], + rationale: 'Nothing depends on it yet. It can steep.' + } + }, + columns: [{ + id: 'diagnosis', + label: 'Diagnosis', + issues: [{ + id: 102, + title: 'Scheduler ignores standing allocation slices', + labels: ['p/2'], + who: 'SM' + }, { + id: 103, + title: 'Dark theme: cone fill too faint', + labels: ['p/4'], + who: 'SM' + }] + }, { + id: 'triage', + label: 'Triage', + issues: [{ + id: 99, + title: 'Directive log: conflict-free merge test', + labels: ['est/1d', 'p/2'], + who: 'SM' + }, { + id: 96, + title: 'Label schema bootstrap for new repos', + labels: ['est/2d', 'p/3'], + who: 'AK' + }, { + id: 78, + title: 'Calibration store cold-start distributions', + labels: ['est/5d', 'p/3'], + who: 'SM' + }] + }, { + id: 'steeping', + label: 'Steeping', + issues: [{ + id: 87, + title: 'Fix lifecycle inference on merge events', + labels: ['est/2d', 'p/1'], + who: 'SM', + blocked: false, + days: '4d' + }, { + id: 84, + title: 'Capacity model: focus factor per person', + labels: ['est/3d', 'p/2'], + who: 'AK', + days: '1d' + }] + }, { + id: 'review', + label: 'In review', + issues: [{ + id: 92, + title: 'Monte Carlo engine: percentile bands', + labels: ['est/5d', 'p/1'], + who: 'SM', + pr: '#141' + }] + }, { + id: 'done', + label: 'Done', + issues: [{ + id: 71, + title: 'Gitea client: token auth + retries', + labels: ['est/2d', 'p/2'], + who: 'SM' + }, { + id: 69, + title: 'pm-state repo bootstrap', + labels: ['est/1d', 'p/1'], + who: 'SM' + }, { + id: 65, + title: 'Electron shell + window state', + labels: ['est/3d', 'p/2'], + who: 'AK' + }] + }], + runway: [{ + name: 'Beta', + due: 'Mar 15', + hard: false, + p80: 'Mar 3–12', + pos: 0.62, + spread: 0.18, + tone: 'ok', + note: 'ahead' + }, { + name: 'Pilot-ready', + due: 'Apr 30', + hard: true, + p80: 'Apr 21 – May 9', + pos: 0.94, + spread: 0.26, + tone: 'warn', + note: 'drifting' + }, { + name: 'v1.0', + due: 'Jun 12', + hard: false, + p80: 'May 30 – Jun 20', + pos: 0.88, + spread: 0.3, + tone: 'ok', + note: 'on watch' + }], + capacity: [{ + who: 'Stephen', + hours: '5.2h/day', + focus: 0.65, + slices: 'dev 70% · pilots 20% · compliance 10%' + }, { + who: 'Ana K.', + hours: '3.8h/day', + focus: 0.8, + slices: 'dev 100%' + }], + chat: [{ + from: 'agent', + text: 'Morning service. Two things drifted overnight; one needs your opinion.' + }, { + from: 'agent', + text: '#84 grew a dependency on #92 \u2014 I\u2019ve reordered. And #87 has been steeping for four days; it blocks two others. Worth a look.' + }, { + from: 'user', + text: 'Push the calibration work to next week, pilots come first' + }, { + from: 'agent', + text: 'Done \u2014 #78 moves to next week. Milestone Beta is unmoved; the cone doesn\u2019t care for calibration either. Logged as a directive.' + }], + cannedReply: 'Noted and logged as a directive. The scheduler is re-running \u2014 I\u2019ll show you the consequence diff in a moment.', + standup: { + date: 'Tuesday 7 July 2026', + drift: [{ + tone: 'warn', + text: '#84 grew a dependency on #92 overnight.', + delta: 'reordered \u00b7 no date impact' + }, { + tone: 'warn', + text: '#92 has sat in review for two days.', + delta: 'Beta 80% +1d if idle past Thu' + }, { + tone: 'ok', + text: 'Webhook outage 02:14\u201302:31; full reconcile ran.', + delta: 'nothing lost' + }], + plan: [{ + who: 'Stephen', + pick: '#87', + title: 'Fix lifecycle inference on merge events', + why: 'It blocks two others and the critical path runs straight through it.' + }, { + who: 'Ana K.', + pick: '#84', + title: 'Capacity model: focus factor per person', + why: 'Already steeping \u2014 finish it before anything new is poured.' + }], + nag: { + id: 87, + days: '4d', + blocks: ['#91', '#92'], + text: 'Four days is a long steep. It blocks #91 and #92 \u2014 worth a look before it stains.' + } + }, + calibration: { + n: 27, + active: true, + labels: [{ + label: 'est/1d', + n: 8, + median: '1.1d', + bias: 8 + }, { + label: 'est/2d', + n: 9, + median: '2.4d', + bias: 18 + }, { + label: 'est/3d', + n: 6, + median: '3.7d', + bias: 22 + }, { + label: 'est/5d', + n: 3, + median: '6.5d', + bias: 30 + }, { + label: 'est/8d', + n: 1, + median: '8.5d', + bias: null + }], + people: [{ + who: 'Stephen', + n: 19, + bias: 21, + note: 'optimism grows with ticket size' + }, { + who: 'Ana K.', + n: 8, + bias: 9, + note: 'close to honest \u2014 suspicious' + }], + scatter: [[1, 1], [1, 1.2], [1, 0.9], [1, 1.5], [1, 1.1], [1, 0.8], [1, 1.3], [1, 1.2], [2, 2], [2, 2.5], [2, 3.1], [2, 2.2], [2, 2.6], [2, 1.8], [2, 2.4], [2, 2.9], [2, 2.3], [3, 3.5], [3, 4.1], [3, 3.1], [3, 4.6], [3, 3.6], [3, 3.9], [5, 6.1], [5, 7.2], [5, 6.4], [8, 8.5]], + fit: 1.18, + effect: { + raw: '42d of estimates', + banded: '80% band 46\u201353d', + p50: '48d' + } + }, + inbox: [{ + id: 1, + day: 'Today', + type: 'drift', + icon: 'chart-line', + tone: 'warn', + text: 'Beta\u2019s 80% window moved', + detail: 'Mar 3\u201312 \u2192 Mar 5\u201314 \u00b7 directive #007', + time: '09:14', + unread: true, + to: 'directives' + }, { + id: 2, + day: 'Today', + type: 'mention', + icon: 'message-square', + tone: 'info', + who: 'Ana K.', + text: 'mentioned you on #92', + detail: '\u201cpercentile bands ready for a second pair of eyes\u201d', + time: '08:52', + unread: true, + issue: { + id: 92, + title: 'Monte Carlo engine: percentile bands', + labels: ['est/5d', 'p/1'] + } + }, { + id: 3, + day: 'Today', + type: 'nag', + icon: 'clock', + tone: 'warn', + text: '#87 is steeping \u00b7 4d', + detail: 'blocks #91 and #92 \u2014 worth a look before it stains', + time: '07:00', + unread: true, + issue: { + id: 87, + title: 'Fix lifecycle inference on merge events', + labels: ['est/2d', 'p/1'], + days: '4d' + } + }, { + id: 4, + day: 'Yesterday', + type: 'review', + icon: 'git-pull-request', + tone: 'info', + text: 'PR #141 awaits review', + detail: '#92 \u00b7 idle two days', + time: '16:20', + unread: false, + issue: { + id: 92, + title: 'Monte Carlo engine: percentile bands', + labels: ['est/5d', 'p/1'] + } + }, { + id: 5, + day: 'Yesterday', + type: 'assignment', + icon: 'user', + tone: 'neutral', + who: 'Ana K.', + text: 'took #96 from the pot', + detail: 'Label schema bootstrap for new repos', + time: '11:03', + unread: false, + issue: { + id: 96, + title: 'Label schema bootstrap for new repos', + labels: ['est/2d', 'p/3'] + } + }, { + id: 6, + day: 'Yesterday', + type: 'system', + icon: 'refresh-cw', + tone: 'ok', + text: 'Webhook outage 02:14\u201302:31', + detail: 'full reconcile ran \u00b7 nothing lost', + time: '02:31', + unread: false + }, { + id: 7, + day: 'Yesterday', + type: 'milestone', + icon: 'milestone', + tone: 'ok', + text: 'P2 \u2014 Scheduler closed two days early', + detail: 'the calibration noticed. So did I.', + time: '09:40', + unread: false + }], + issueDetail: { + 87: { + state: 'steeping', + assignee: 'Stephen', + milestone: 'Beta', + body: 'Squash-merges emit events out of order when CI runs long: the PR-closed webhook lands before the merge event, so lifecycle inference marks deploy before work-end and the actuals go negative. Regular merges infer correctly.', + comments: [{ + who: 'Ana K.', + when: 'Feb 8 \u00b7 14:12', + text: 'Repro: squash-merge only. Regular merges infer fine.' + }, { + who: 'Stephen', + when: 'Feb 9 \u00b7 09:30', + text: 'Confirmed \u2014 the event order flips whenever CI takes more than ~5 minutes.' + }], + lifecycle: [{ + stage: 'Diagnosis', + event: 'issue opened', + when: 'Feb 2 \u00b7 09:14', + icon: 'circle-dot', + done: true + }, { + stage: 'Triage', + event: 'labeled est/2d \u00b7 milestoned Beta', + when: 'Feb 3 \u00b7 10:02', + icon: 'tag', + done: true + }, { + stage: 'Work start', + event: 'first commit ref a41f09', + when: 'Feb 6 \u00b7 11:47', + icon: 'git-commit-horizontal', + done: true + }, { + stage: 'Deploy', + event: 'PR merged', + when: 'pending', + icon: 'git-merge', + done: false + }, { + stage: 'Complete', + event: 'issue closed', + when: 'pending', + icon: 'circle-check', + done: false + }], + forecast: { + p80: 'done Feb 11\u201313', + note: 'from your est/2d history \u00b7 n=14' + }, + blocks: [91, 92], + blockedBy: [], + note: 'It blocks #91 and #92. I\u2019d take it first \u2014 the critical path agrees with me.' + } + }, + directives: { + pending: { + seq: 7, + who: 'Stephen', + when: 'today 09:12', + what: 'Pilots before calibration \u2014 push #78 to next week.', + diff: [{ + tone: 'info', + change: '#78 Calibration store', + from: 'this week', + to: 'wk of Feb 23' + }, { + tone: 'warn', + change: 'Beta \u00b7 80% window', + from: 'Mar 3\u201312', + to: 'Mar 5\u201314' + }, { + tone: 'ok', + change: "Today's plan", + from: '#87', + to: '#87 \u00b7 unchanged' + }] + }, + entries: [{ + seq: 6, + who: 'Stephen', + when: 'Feb 8 \u00b7 16:40', + what: 'Ana takes nothing new until #84 lands.', + why: 'context thrash', + status: 'applied', + consequence: 'WIP capped \u00b7 v1.0 unmoved' + }, { + seq: 5, + who: 'Stephen', + when: 'Feb 6 \u00b7 09:03', + what: 'Ship Beta a week early.', + why: 'board meeting', + status: 'withdrawn', + consequence: '80% would need scope \u22129d \u2014 withdrawn after diff' + }, { + seq: 4, + who: 'Stephen', + when: 'Feb 3 \u00b7 11:21', + what: 'deadline/hard on Pilot-ready.', + why: 'contract date', + status: 'applied', + consequence: 'label applied \u00b7 runway flag raised' + }, { + seq: 3, + who: 'Stephen', + when: 'Jan 28 \u00b7 08:47', + what: 'Webhook work ahead of UI polish.', + why: '', + status: 'applied', + consequence: '#91 +2 ranks \u00b7 Beta unmoved' + }, { + seq: 2, + who: 'Stephen', + when: 'Jan 20 \u00b7 14:02', + what: 'Estimates in days, never hours.', + why: 'sanity', + status: 'applied', + consequence: 'label schema est/* confirmed' + }, { + seq: 1, + who: 'Stephen', + when: 'Jan 19 \u00b7 09:00', + what: 'CommiTea manages its own backlog.', + why: 'dogfood', + status: 'applied', + consequence: 'stephen/commitea under management' + }] + }, + gantt: { + // day offsets from Feb 2; chart spans 42 days (Feb 2 – Mar 16) + days: 42, + weeks: [{ + at: 0, + label: 'Feb 2' + }, { + at: 7, + label: 'Feb 9' + }, { + at: 14, + label: 'Feb 16' + }, { + at: 21, + label: 'Feb 23' + }, { + at: 28, + label: 'Mar 2' + }, { + at: 35, + label: 'Mar 9' + }, { + at: 41, + label: 'Mar 15' + }], + today: 8, + band: { + from: 29, + to: 38, + label: '80% · Mar 3–12' + }, + due: { + at: 41, + label: 'Beta due' + }, + rows: [{ + id: 71, + title: 'Gitea client: token auth + retries', + who: 'SM', + state: 'done', + start: 0, + end: 4 + }, { + id: 69, + title: 'pm-state repo bootstrap', + who: 'SM', + state: 'done', + start: 0, + end: 1 + }, { + id: 87, + title: 'Fix lifecycle inference on merge events', + who: 'SM', + state: 'steeping', + start: 4, + end: 9, + p80: 11, + crit: true + }, { + id: 92, + title: 'Monte Carlo engine: percentile bands', + who: 'SM', + state: 'review', + start: 9, + end: 18, + p80: 21, + crit: true + }, { + id: 91, + title: 'Webhook listener: reconcile on reconnect', + who: 'AK', + state: 'scheduled', + start: 9, + end: 14, + p80: 16 + }, { + id: 99, + title: 'Directive log: conflict-free merge test', + who: 'SM', + state: 'scheduled', + start: 10, + end: 11 + }, { + id: 96, + title: 'Label schema bootstrap for new repos', + who: 'AK', + state: 'scheduled', + start: 14, + end: 16 + }, { + id: 84, + title: 'Capacity model: focus factor per person', + who: 'AK', + state: 'scheduled', + start: 18, + end: 23, + p80: 26, + crit: true + }, { + id: 102, + title: 'Scheduler ignores standing allocation slices', + who: 'SM', + state: 'scheduled', + start: 23, + end: 25, + p80: 28, + crit: true + }, { + id: 78, + title: 'Calibration store cold-start distributions', + who: 'SM', + state: 'scheduled', + start: 25, + end: 30 + }] + }, + deps: { + nodes: [{ + id: 71, + title: 'Gitea client: token auth + retries', + tags: ['est/2d'], + state: 'done', + col: 0, + row: 0.2 + }, { + id: 69, + title: 'pm-state repo bootstrap', + tags: ['est/1d'], + state: 'done', + col: 0, + row: 1.9 + }, { + id: 87, + title: 'Fix lifecycle inference on merge events', + tags: ['est/2d', 'p/1'], + state: 'steeping', + days: '4d', + col: 1, + row: 0.7, + rationale: 'It blocks #91 and #92. I\u2019d take it first \u2014 the critical path agrees with me.' + }, { + id: 99, + title: 'Directive log: conflict-free merge test', + tags: ['est/1d', 'p/2'], + state: 'triage', + col: 1, + row: 1.9 + }, { + id: 91, + title: 'Webhook listener: reconcile on reconnect', + tags: ['est/3d', 'p/2'], + state: 'triage', + col: 2, + row: 0 + }, { + id: 92, + title: 'Monte Carlo engine: percentile bands', + tags: ['est/5d', 'p/1'], + state: 'review', + col: 2, + row: 1.4 + }, { + id: 84, + title: 'Capacity model: focus factor per person', + tags: ['est/3d', 'p/2'], + state: 'steeping', + days: '1d', + col: 3, + row: 1.4 + }, { + id: 102, + title: 'Scheduler ignores standing allocation slices', + tags: ['p/2'], + state: 'diagnosis', + col: 4, + row: 1.4 + }], + milestone: { + name: 'Beta', + due: 'Mar 15', + col: 5, + row: 1.4 + }, + edges: [{ + from: 71, + to: 87 + }, { + from: 69, + to: 99 + }, { + from: 87, + to: 91 + }, { + from: 87, + to: 92, + crit: true + }, { + from: 92, + to: 84, + crit: true + }, { + from: 84, + to: 102, + crit: true + }, { + from: 102, + to: 'ms', + crit: true + }], + critical: [87, 92, 84, 102], + unattached: [78, 96, 103, 65] + } +}; +})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/app/data.js", error: String((e && e.message) || e) }); } + +__ds_ns.Badge = __ds_scope.Badge; + +__ds_ns.Button = __ds_scope.Button; + +__ds_ns.Card = __ds_scope.Card; + +__ds_ns.Icon = __ds_scope.Icon; + +__ds_ns.ICON_NAMES = __ds_scope.ICON_NAMES; + +__ds_ns.IconButton = __ds_scope.IconButton; + +__ds_ns.Tabs = __ds_scope.Tabs; + +__ds_ns.Tag = __ds_scope.Tag; + +__ds_ns.Dialog = __ds_scope.Dialog; + +__ds_ns.Toast = __ds_scope.Toast; + +__ds_ns.Tooltip = __ds_scope.Tooltip; + +__ds_ns.Checkbox = __ds_scope.Checkbox; + +__ds_ns.Input = __ds_scope.Input; + +__ds_ns.Radio = __ds_scope.Radio; + +__ds_ns.Select = __ds_scope.Select; + +__ds_ns.Switch = __ds_scope.Switch; + +})(); diff --git a/docs/design/assets/fonts/caslon-display-normal-400.woff2 b/docs/design/assets/fonts/caslon-display-normal-400.woff2 new file mode 100644 index 0000000..ad36393 Binary files /dev/null and b/docs/design/assets/fonts/caslon-display-normal-400.woff2 differ diff --git a/docs/design/assets/fonts/caslon-text-italic-400.woff2 b/docs/design/assets/fonts/caslon-text-italic-400.woff2 new file mode 100644 index 0000000..b098c59 Binary files /dev/null and b/docs/design/assets/fonts/caslon-text-italic-400.woff2 differ diff --git a/docs/design/assets/fonts/caslon-text-normal-400.woff2 b/docs/design/assets/fonts/caslon-text-normal-400.woff2 new file mode 100644 index 0000000..3850132 Binary files /dev/null and b/docs/design/assets/fonts/caslon-text-normal-400.woff2 differ diff --git a/docs/design/assets/fonts/caslon-text-normal-700.woff2 b/docs/design/assets/fonts/caslon-text-normal-700.woff2 new file mode 100644 index 0000000..70f4b22 Binary files /dev/null and b/docs/design/assets/fonts/caslon-text-normal-700.woff2 differ diff --git a/docs/design/assets/fonts/instrument-sans-italic-400-700.woff2 b/docs/design/assets/fonts/instrument-sans-italic-400-700.woff2 new file mode 100644 index 0000000..b4f9b28 Binary files /dev/null and b/docs/design/assets/fonts/instrument-sans-italic-400-700.woff2 differ diff --git a/docs/design/assets/fonts/instrument-sans-normal-400-700.woff2 b/docs/design/assets/fonts/instrument-sans-normal-400-700.woff2 new file mode 100644 index 0000000..665fa65 Binary files /dev/null and b/docs/design/assets/fonts/instrument-sans-normal-400-700.woff2 differ diff --git a/docs/design/assets/fonts/plex-mono-normal-400.woff2 b/docs/design/assets/fonts/plex-mono-normal-400.woff2 new file mode 100644 index 0000000..52b6c75 Binary files /dev/null and b/docs/design/assets/fonts/plex-mono-normal-400.woff2 differ diff --git a/docs/design/assets/fonts/plex-mono-normal-500.woff2 b/docs/design/assets/fonts/plex-mono-normal-500.woff2 new file mode 100644 index 0000000..3308bce Binary files /dev/null and b/docs/design/assets/fonts/plex-mono-normal-500.woff2 differ diff --git a/docs/design/assets/fonts/plex-mono-normal-600.woff2 b/docs/design/assets/fonts/plex-mono-normal-600.woff2 new file mode 100644 index 0000000..c6759ef Binary files /dev/null and b/docs/design/assets/fonts/plex-mono-normal-600.woff2 differ diff --git a/docs/design/assets/icons/activity.svg b/docs/design/assets/icons/activity.svg new file mode 100644 index 0000000..cf3f8d4 --- /dev/null +++ b/docs/design/assets/icons/activity.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/design/assets/icons/arrow-left.svg b/docs/design/assets/icons/arrow-left.svg new file mode 100644 index 0000000..ca61f09 --- /dev/null +++ b/docs/design/assets/icons/arrow-left.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/arrow-right.svg b/docs/design/assets/icons/arrow-right.svg new file mode 100644 index 0000000..314b2cd --- /dev/null +++ b/docs/design/assets/icons/arrow-right.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/arrow-up-right.svg b/docs/design/assets/icons/arrow-up-right.svg new file mode 100644 index 0000000..ae714d6 --- /dev/null +++ b/docs/design/assets/icons/arrow-up-right.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/bell.svg b/docs/design/assets/icons/bell.svg new file mode 100644 index 0000000..8283cf6 --- /dev/null +++ b/docs/design/assets/icons/bell.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/calendar.svg b/docs/design/assets/icons/calendar.svg new file mode 100644 index 0000000..d7f82e7 --- /dev/null +++ b/docs/design/assets/icons/calendar.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/chart-line.svg b/docs/design/assets/icons/chart-line.svg new file mode 100644 index 0000000..5f50974 --- /dev/null +++ b/docs/design/assets/icons/chart-line.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/chart-no-axes-gantt.svg b/docs/design/assets/icons/chart-no-axes-gantt.svg new file mode 100644 index 0000000..118bf50 --- /dev/null +++ b/docs/design/assets/icons/chart-no-axes-gantt.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/check.svg b/docs/design/assets/icons/check.svg new file mode 100644 index 0000000..92f4df3 --- /dev/null +++ b/docs/design/assets/icons/check.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/design/assets/icons/chevron-down.svg b/docs/design/assets/icons/chevron-down.svg new file mode 100644 index 0000000..b627264 --- /dev/null +++ b/docs/design/assets/icons/chevron-down.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/design/assets/icons/chevron-left.svg b/docs/design/assets/icons/chevron-left.svg new file mode 100644 index 0000000..c99c66b --- /dev/null +++ b/docs/design/assets/icons/chevron-left.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/design/assets/icons/chevron-right.svg b/docs/design/assets/icons/chevron-right.svg new file mode 100644 index 0000000..538a173 --- /dev/null +++ b/docs/design/assets/icons/chevron-right.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/design/assets/icons/chevron-up.svg b/docs/design/assets/icons/chevron-up.svg new file mode 100644 index 0000000..efeec90 --- /dev/null +++ b/docs/design/assets/icons/chevron-up.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/design/assets/icons/circle-alert.svg b/docs/design/assets/icons/circle-alert.svg new file mode 100644 index 0000000..61e2a97 --- /dev/null +++ b/docs/design/assets/icons/circle-alert.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/circle-check.svg b/docs/design/assets/icons/circle-check.svg new file mode 100644 index 0000000..2783d3b --- /dev/null +++ b/docs/design/assets/icons/circle-check.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/circle-dashed.svg b/docs/design/assets/icons/circle-dashed.svg new file mode 100644 index 0000000..eb1e1c9 --- /dev/null +++ b/docs/design/assets/icons/circle-dashed.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/circle-dot.svg b/docs/design/assets/icons/circle-dot.svg new file mode 100644 index 0000000..70ae1b3 --- /dev/null +++ b/docs/design/assets/icons/circle-dot.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/circle.svg b/docs/design/assets/icons/circle.svg new file mode 100644 index 0000000..86d05a4 --- /dev/null +++ b/docs/design/assets/icons/circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/design/assets/icons/clock-3.svg b/docs/design/assets/icons/clock-3.svg new file mode 100644 index 0000000..8d752c3 --- /dev/null +++ b/docs/design/assets/icons/clock-3.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/clock.svg b/docs/design/assets/icons/clock.svg new file mode 100644 index 0000000..5581397 --- /dev/null +++ b/docs/design/assets/icons/clock.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/coffee.svg b/docs/design/assets/icons/coffee.svg new file mode 100644 index 0000000..83944ea --- /dev/null +++ b/docs/design/assets/icons/coffee.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/copy.svg b/docs/design/assets/icons/copy.svg new file mode 100644 index 0000000..b0e5a27 --- /dev/null +++ b/docs/design/assets/icons/copy.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/ellipsis.svg b/docs/design/assets/icons/ellipsis.svg new file mode 100644 index 0000000..a3ca7d4 --- /dev/null +++ b/docs/design/assets/icons/ellipsis.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/external-link.svg b/docs/design/assets/icons/external-link.svg new file mode 100644 index 0000000..a9bb97d --- /dev/null +++ b/docs/design/assets/icons/external-link.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/eye.svg b/docs/design/assets/icons/eye.svg new file mode 100644 index 0000000..182cbd4 --- /dev/null +++ b/docs/design/assets/icons/eye.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/filter.svg b/docs/design/assets/icons/filter.svg new file mode 100644 index 0000000..24dc440 --- /dev/null +++ b/docs/design/assets/icons/filter.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/design/assets/icons/flag.svg b/docs/design/assets/icons/flag.svg new file mode 100644 index 0000000..d24c6d1 --- /dev/null +++ b/docs/design/assets/icons/flag.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/gauge.svg b/docs/design/assets/icons/gauge.svg new file mode 100644 index 0000000..34f5aac --- /dev/null +++ b/docs/design/assets/icons/gauge.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/git-branch.svg b/docs/design/assets/icons/git-branch.svg new file mode 100644 index 0000000..3b6b9a0 --- /dev/null +++ b/docs/design/assets/icons/git-branch.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/git-commit-horizontal.svg b/docs/design/assets/icons/git-commit-horizontal.svg new file mode 100644 index 0000000..7cd97cc --- /dev/null +++ b/docs/design/assets/icons/git-commit-horizontal.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/git-merge.svg b/docs/design/assets/icons/git-merge.svg new file mode 100644 index 0000000..5a656c3 --- /dev/null +++ b/docs/design/assets/icons/git-merge.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/git-pull-request.svg b/docs/design/assets/icons/git-pull-request.svg new file mode 100644 index 0000000..ce1791a --- /dev/null +++ b/docs/design/assets/icons/git-pull-request.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/history.svg b/docs/design/assets/icons/history.svg new file mode 100644 index 0000000..965a7b5 --- /dev/null +++ b/docs/design/assets/icons/history.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/inbox.svg b/docs/design/assets/icons/inbox.svg new file mode 100644 index 0000000..6bbfd52 --- /dev/null +++ b/docs/design/assets/icons/inbox.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/info.svg b/docs/design/assets/icons/info.svg new file mode 100644 index 0000000..3399cf4 --- /dev/null +++ b/docs/design/assets/icons/info.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/keyboard.svg b/docs/design/assets/icons/keyboard.svg new file mode 100644 index 0000000..b54d0cb --- /dev/null +++ b/docs/design/assets/icons/keyboard.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/layers.svg b/docs/design/assets/icons/layers.svg new file mode 100644 index 0000000..e5608f9 --- /dev/null +++ b/docs/design/assets/icons/layers.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/leaf.svg b/docs/design/assets/icons/leaf.svg new file mode 100644 index 0000000..60b534d --- /dev/null +++ b/docs/design/assets/icons/leaf.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/link.svg b/docs/design/assets/icons/link.svg new file mode 100644 index 0000000..cddf8de --- /dev/null +++ b/docs/design/assets/icons/link.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/list-filter.svg b/docs/design/assets/icons/list-filter.svg new file mode 100644 index 0000000..697be73 --- /dev/null +++ b/docs/design/assets/icons/list-filter.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/list.svg b/docs/design/assets/icons/list.svg new file mode 100644 index 0000000..b6b3e43 --- /dev/null +++ b/docs/design/assets/icons/list.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/loader-circle.svg b/docs/design/assets/icons/loader-circle.svg new file mode 100644 index 0000000..20279ce --- /dev/null +++ b/docs/design/assets/icons/loader-circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/design/assets/icons/message-square.svg b/docs/design/assets/icons/message-square.svg new file mode 100644 index 0000000..383403f --- /dev/null +++ b/docs/design/assets/icons/message-square.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/design/assets/icons/milestone.svg b/docs/design/assets/icons/milestone.svg new file mode 100644 index 0000000..4333f7f --- /dev/null +++ b/docs/design/assets/icons/milestone.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/minus.svg b/docs/design/assets/icons/minus.svg new file mode 100644 index 0000000..e941e5f --- /dev/null +++ b/docs/design/assets/icons/minus.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/design/assets/icons/moon.svg b/docs/design/assets/icons/moon.svg new file mode 100644 index 0000000..3ef47ba --- /dev/null +++ b/docs/design/assets/icons/moon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/design/assets/icons/network.svg b/docs/design/assets/icons/network.svg new file mode 100644 index 0000000..3b3f280 --- /dev/null +++ b/docs/design/assets/icons/network.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/panel-left.svg b/docs/design/assets/icons/panel-left.svg new file mode 100644 index 0000000..81990ea --- /dev/null +++ b/docs/design/assets/icons/panel-left.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/pause.svg b/docs/design/assets/icons/pause.svg new file mode 100644 index 0000000..0c830f8 --- /dev/null +++ b/docs/design/assets/icons/pause.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/pencil.svg b/docs/design/assets/icons/pencil.svg new file mode 100644 index 0000000..f353f82 --- /dev/null +++ b/docs/design/assets/icons/pencil.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/play.svg b/docs/design/assets/icons/play.svg new file mode 100644 index 0000000..097b7b8 --- /dev/null +++ b/docs/design/assets/icons/play.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/design/assets/icons/plus.svg b/docs/design/assets/icons/plus.svg new file mode 100644 index 0000000..e1280bc --- /dev/null +++ b/docs/design/assets/icons/plus.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/refresh-cw.svg b/docs/design/assets/icons/refresh-cw.svg new file mode 100644 index 0000000..7c321ae --- /dev/null +++ b/docs/design/assets/icons/refresh-cw.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/search.svg b/docs/design/assets/icons/search.svg new file mode 100644 index 0000000..49ff049 --- /dev/null +++ b/docs/design/assets/icons/search.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/send.svg b/docs/design/assets/icons/send.svg new file mode 100644 index 0000000..95c2c70 --- /dev/null +++ b/docs/design/assets/icons/send.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/settings-2.svg b/docs/design/assets/icons/settings-2.svg new file mode 100644 index 0000000..634bb98 --- /dev/null +++ b/docs/design/assets/icons/settings-2.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/settings.svg b/docs/design/assets/icons/settings.svg new file mode 100644 index 0000000..1bd15d5 --- /dev/null +++ b/docs/design/assets/icons/settings.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/sparkles.svg b/docs/design/assets/icons/sparkles.svg new file mode 100644 index 0000000..ca74b84 --- /dev/null +++ b/docs/design/assets/icons/sparkles.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/square-kanban.svg b/docs/design/assets/icons/square-kanban.svg new file mode 100644 index 0000000..bf1a8e3 --- /dev/null +++ b/docs/design/assets/icons/square-kanban.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/sun.svg b/docs/design/assets/icons/sun.svg new file mode 100644 index 0000000..83fb8bb --- /dev/null +++ b/docs/design/assets/icons/sun.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/tag.svg b/docs/design/assets/icons/tag.svg new file mode 100644 index 0000000..53b2bb4 --- /dev/null +++ b/docs/design/assets/icons/tag.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/target.svg b/docs/design/assets/icons/target.svg new file mode 100644 index 0000000..3061a89 --- /dev/null +++ b/docs/design/assets/icons/target.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/trash-2.svg b/docs/design/assets/icons/trash-2.svg new file mode 100644 index 0000000..4bbb166 --- /dev/null +++ b/docs/design/assets/icons/trash-2.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/trending-up.svg b/docs/design/assets/icons/trending-up.svg new file mode 100644 index 0000000..0bca7ef --- /dev/null +++ b/docs/design/assets/icons/trending-up.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/triangle-alert.svg b/docs/design/assets/icons/triangle-alert.svg new file mode 100644 index 0000000..4601160 --- /dev/null +++ b/docs/design/assets/icons/triangle-alert.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/user.svg b/docs/design/assets/icons/user.svg new file mode 100644 index 0000000..bc66de3 --- /dev/null +++ b/docs/design/assets/icons/user.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/users.svg b/docs/design/assets/icons/users.svg new file mode 100644 index 0000000..6a708fe --- /dev/null +++ b/docs/design/assets/icons/users.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/x.svg b/docs/design/assets/icons/x.svg new file mode 100644 index 0000000..4eb59f3 --- /dev/null +++ b/docs/design/assets/icons/x.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/design/assets/icons/zap.svg b/docs/design/assets/icons/zap.svg new file mode 100644 index 0000000..f750eb0 --- /dev/null +++ b/docs/design/assets/icons/zap.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/design/assets/logo-icon.png b/docs/design/assets/logo-icon.png new file mode 100644 index 0000000..8f91eee Binary files /dev/null and b/docs/design/assets/logo-icon.png differ diff --git a/docs/design/assets/logo.jpeg b/docs/design/assets/logo.jpeg new file mode 100644 index 0000000..951dd2a Binary files /dev/null and b/docs/design/assets/logo.jpeg differ diff --git a/docs/design/components/core/Badge.d-ts.txt b/docs/design/components/core/Badge.d-ts.txt new file mode 100644 index 0000000..20e4539 --- /dev/null +++ b/docs/design/components/core/Badge.d-ts.txt @@ -0,0 +1,12 @@ +/** + * Status pill — muted tint background, colored text. For state words like "ahead", "behind", "steeping". + */ +export interface BadgeProps { + /** @default 'neutral' */ + tone?: 'ok' | 'warn' | 'danger' | 'info' | 'neutral' | 'jade'; + /** Leading status dot @default false */ + dot?: boolean; + children?: React.ReactNode; + style?: React.CSSProperties; +} +export declare function Badge(props: BadgeProps): JSX.Element; diff --git a/docs/design/components/core/Badge.js.txt b/docs/design/components/core/Badge.js.txt new file mode 100644 index 0000000..d6e3580 --- /dev/null +++ b/docs/design/components/core/Badge.js.txt @@ -0,0 +1,32 @@ +import React from 'react'; + +const CSS = ` +.ct-badge { + display: inline-flex; align-items: center; gap: 6px; + font: 500 12px/1 var(--font-sans); + padding: 4px 9px; + border-radius: var(--radius-round); +} +.ct-badge__dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; } +.ct-badge--ok { background: var(--ok-tint); color: var(--ok); } +.ct-badge--warn { background: var(--warn-tint); color: var(--warn); } +.ct-badge--danger { background: var(--danger-tint); color: var(--danger); } +.ct-badge--info { background: var(--info-tint); color: var(--info); } +.ct-badge--neutral { background: var(--paper-2); color: var(--ink-2); } +.ct-badge--jade { background: var(--jade-tint); color: var(--jade-7); } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-badge-css')) { + const s = document.createElement('style'); s.id = 'ct-badge-css'; s.textContent = CSS; + document.head.appendChild(s); + } +})(); + +export function Badge({ tone = 'neutral', dot = false, children, style, ...rest }) { + return ( + + {dot ? : null} + {children} + + ); +} diff --git a/docs/design/components/core/Badge.prompt.md b/docs/design/components/core/Badge.prompt.md new file mode 100644 index 0000000..cc21320 --- /dev/null +++ b/docs/design/components/core/Badge.prompt.md @@ -0,0 +1,9 @@ +Status pill for state words (ahead / behind / steeping / blocked); sans text on a muted tint. + +```jsx +ahead +drifting +blocked +``` + +Tones: ok, warn, danger, info, neutral, jade (jade = agent/forecast flavor). Use `Tag` instead for verbatim gitea labels like `est/3d`. diff --git a/docs/design/components/core/Button.d-ts.txt b/docs/design/components/core/Button.d-ts.txt new file mode 100644 index 0000000..4f75749 --- /dev/null +++ b/docs/design/components/core/Button.d-ts.txt @@ -0,0 +1,18 @@ +/** + * CommiTea button. Plain verbs only ("Approve", "Brew plan", "Defer") — never witty. + */ +export interface ButtonProps { + /** @default 'primary' */ + variant?: 'primary' | 'secondary' | 'ghost' | 'danger'; + /** @default 'md' */ + size?: 'sm' | 'md'; + /** Lucide icon name rendered before the label */ + icon?: string; + /** Lucide icon name rendered after the label */ + iconRight?: string; + disabled?: boolean; + onClick?: (e: React.MouseEvent) => void; + children?: React.ReactNode; + style?: React.CSSProperties; +} +export declare function Button(props: ButtonProps): JSX.Element; diff --git a/docs/design/components/core/Button.js.txt b/docs/design/components/core/Button.js.txt new file mode 100644 index 0000000..0ce3545 --- /dev/null +++ b/docs/design/components/core/Button.js.txt @@ -0,0 +1,63 @@ +import React from 'react'; + +const CSS = ` +.ct-btn { + display: inline-flex; align-items: center; justify-content: center; gap: 8px; + font: 600 14px/1 var(--font-sans); + border-radius: var(--radius-2); + border: 1px solid transparent; + cursor: pointer; + white-space: nowrap; + transition: background var(--duration-fast) var(--ease-out), + border-color var(--duration-fast) var(--ease-out), + color var(--duration-fast) var(--ease-out); +} +.ct-btn:disabled { opacity: 0.45; cursor: not-allowed; } +.ct-btn--md { height: 34px; padding: 0 14px; } +.ct-btn--sm { height: 28px; padding: 0 10px; font-size: 13px; } +.ct-btn--primary { background: var(--accent); color: var(--text-on-accent); } +.ct-btn--primary:hover:not(:disabled) { background: var(--accent-hover); } +.ct-btn--primary:active:not(:disabled) { background: var(--accent-pressed); } +.ct-btn--secondary { background: var(--surface-card); border-color: var(--line-2); color: var(--ink-1); } +.ct-btn--secondary:hover:not(:disabled) { background: var(--paper-2); } +.ct-btn--secondary:active:not(:disabled) { background: var(--paper-3); } +.ct-btn--ghost { background: transparent; color: var(--ink-2); } +.ct-btn--ghost:hover:not(:disabled) { background: var(--paper-2); color: var(--ink-1); } +.ct-btn--ghost:active:not(:disabled) { background: var(--paper-3); } +.ct-btn--danger { background: var(--danger); color: var(--ink-inverse); } +.ct-btn--danger:hover:not(:disabled) { filter: brightness(0.92); } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-button-css')) { + const s = document.createElement('style'); s.id = 'ct-button-css'; s.textContent = CSS; + document.head.appendChild(s); + } +})(); + +import { Icon } from './Icon'; + +export function Button({ + variant = 'primary', + size = 'md', + icon, + iconRight, + disabled = false, + children, + style, + ...rest +}) { + const iconSize = size === 'sm' ? 15 : 17; + return ( + + ); +} diff --git a/docs/design/components/core/Button.prompt.md b/docs/design/components/core/Button.prompt.md new file mode 100644 index 0000000..7ab2ce9 --- /dev/null +++ b/docs/design/components/core/Button.prompt.md @@ -0,0 +1,10 @@ +Button for all actions; labels are plain sentence-case verbs — the wit lives elsewhere. + +```jsx + + + + +``` + +Variants: `primary` (spruce fill — one per view), `secondary` (hairline outline), `ghost` (bare), `danger` (madder red, destructive confirms only). Sizes `md` 34px / `sm` 28px. `icon`/`iconRight` take Lucide names. diff --git a/docs/design/components/core/Card.d-ts.txt b/docs/design/components/core/Card.d-ts.txt new file mode 100644 index 0000000..c396cff --- /dev/null +++ b/docs/design/components/core/Card.d-ts.txt @@ -0,0 +1,21 @@ +/** + * Paper card — hairline border, 10px radius, whispered shadow. `jade` adds the jade top rule + * reserved for the view's key card (e.g. the Focus card). + */ +export interface CardProps { + /** Caslon title */ + title?: React.ReactNode; + /** Small uppercase label above the title */ + overline?: string; + /** Right-aligned header actions (IconButtons) */ + actions?: React.ReactNode; + /** Footer row below a hairline */ + footer?: React.ReactNode; + /** Jade top rule — one per view @default false */ + jade?: boolean; + /** Remove body padding (tables, charts) @default false */ + flush?: boolean; + children?: React.ReactNode; + style?: React.CSSProperties; +} +export declare function Card(props: CardProps): JSX.Element; diff --git a/docs/design/components/core/Card.js.txt b/docs/design/components/core/Card.js.txt new file mode 100644 index 0000000..383cac3 --- /dev/null +++ b/docs/design/components/core/Card.js.txt @@ -0,0 +1,58 @@ +import React from 'react'; + +const CSS = ` +.ct-card { + background: var(--surface-card); + border: 1px solid var(--border-hairline); + border-radius: var(--radius-3); + box-shadow: var(--shadow-1); +} +.ct-card--jade { box-shadow: var(--shadow-jade-line), var(--shadow-1); } +.ct-card__header { + display: flex; align-items: baseline; justify-content: space-between; gap: 12px; + padding: 16px 20px 0; +} +.ct-card__title { + font: var(--text-title); + color: var(--ink-1); + margin: 0; +} +.ct-card__overline { + font: var(--text-overline); + letter-spacing: var(--letter-spacing-wide); + text-transform: uppercase; + color: var(--ink-3); + margin: 0 0 6px; +} +.ct-card__body { padding: 14px 20px 18px; } +.ct-card__body--flush { padding: 0; } +.ct-card__footer { + display: flex; align-items: center; gap: 8px; + padding: 12px 20px; + border-top: 1px solid var(--border-hairline); +} +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-card-css')) { + const s = document.createElement('style'); s.id = 'ct-card-css'; s.textContent = CSS; + document.head.appendChild(s); + } +})(); + +export function Card({ title, overline, actions, footer, jade = false, flush = false, children, style, ...rest }) { + return ( +
+ {(title || overline || actions) ? ( +
+
+ {overline ?

{overline}

: null} + {title ?

{title}

: null} +
+ {actions ?
{actions}
: null} +
+ ) : null} +
{children}
+ {footer ?
{footer}
: null} +
+ ); +} diff --git a/docs/design/components/core/Card.prompt.md b/docs/design/components/core/Card.prompt.md new file mode 100644 index 0000000..dd3e602 --- /dev/null +++ b/docs/design/components/core/Card.prompt.md @@ -0,0 +1,11 @@ +Paper card container; title is set in Caslon, elevation is whispered. + +```jsx +} + footer={}> + Body content. + +``` + +`jade` (jade top rule) marks the single most important card in a view. `flush` removes body padding for tables/charts. diff --git a/docs/design/components/core/Icon.d-ts.txt b/docs/design/components/core/Icon.d-ts.txt new file mode 100644 index 0000000..aab00c0 --- /dev/null +++ b/docs/design/components/core/Icon.d-ts.txt @@ -0,0 +1,15 @@ +/** + * Inline Lucide icon (1.5px stroke), tinted by currentColor. + */ +export interface IconProps { + /** Lucide icon name, e.g. "circle-dot", "sparkles", "git-branch" */ + name: string; + /** Square size in px. 16 inline, 18–20 in buttons/nav. @default 16 */ + size?: number; + /** @default 1.5 */ + strokeWidth?: number; + /** Accessible label; omit for decorative icons */ + title?: string; + style?: React.CSSProperties; +} +export declare function Icon(props: IconProps): JSX.Element; diff --git a/docs/design/components/core/Icon.js.txt b/docs/design/components/core/Icon.js.txt new file mode 100644 index 0000000..daf8ed7 --- /dev/null +++ b/docs/design/components/core/Icon.js.txt @@ -0,0 +1,32 @@ +import React from 'react'; + +/* Generated from assets/icons/*.svg (lucide-static v0.462.0, ISC). Do not edit paths by hand. */ +const ICONS = {"activity":"","arrow-left":"\n ","arrow-right":"\n ","arrow-up-right":"\n ","bell":"\n ","calendar":"\n \n \n ","chart-line":"\n ","chart-no-axes-gantt":"\n \n ","check":"","chevron-down":"","chevron-left":"","chevron-right":"","chevron-up":"","circle-alert":"\n \n ","circle-check":"\n ","circle-dashed":"\n \n \n \n \n \n \n ","circle-dot":"\n ","circle":"","clock-3":"\n ","clock":"\n ","coffee":"\n \n \n ","copy":"\n ","ellipsis":"\n \n ","external-link":"\n \n ","eye":"\n ","filter":"","flag":"\n ","gauge":"\n ","git-branch":"\n \n \n ","git-commit-horizontal":"\n \n ","git-merge":"\n \n ","git-pull-request":"\n \n \n ","history":"\n \n ","inbox":"\n ","info":"\n \n ","keyboard":"\n \n \n \n \n \n \n \n ","layers":"\n \n ","leaf":"\n ","link":"\n ","list-filter":"\n \n ","list":"\n \n \n \n \n ","loader-circle":"","message-square":"","milestone":"\n \n ","minus":"","moon":"","network":"\n \n \n \n ","panel-left":"\n ","pause":"\n ","pencil":"\n ","play":"","plus":"\n ","refresh-cw":"\n \n \n ","search":"\n ","send":"\n ","settings-2":"\n \n \n ","settings":"\n ","sparkles":"\n \n \n \n ","square-kanban":"\n \n \n ","sun":"\n \n \n \n \n \n \n \n ","tag":"\n ","target":"\n \n ","trash-2":"\n \n \n \n ","trending-up":"\n ","triangle-alert":"\n \n ","user":"\n ","users":"\n \n \n ","x":"\n ","zap":""}; + +export function Icon({ name, size = 16, strokeWidth = 1.5, title, style, ...rest }) { + const inner = ICONS[name]; + if (!inner) { + console.warn('[CommiTea Icon] unknown icon: ' + name); + return null; + } + return ( + + ); +} + +export const ICON_NAMES = Object.keys(ICONS); diff --git a/docs/design/components/core/Icon.prompt.md b/docs/design/components/core/Icon.prompt.md new file mode 100644 index 0000000..c862015 --- /dev/null +++ b/docs/design/components/core/Icon.prompt.md @@ -0,0 +1,8 @@ +Inline SVG icon from the local Lucide set (69 icons, 1.5px stroke); color inherits currentColor. + +```jsx + + +``` + +Domain mapping: issues circle-dot · milestones milestone · board square-kanban · forecast chart-line · deps network · agent sparkles · standup coffee · capacity gauge · directives flag. ICON_NAMES export lists all available names. Icons are decorative by default (aria-hidden); pass title for meaningful ones. diff --git a/docs/design/components/core/IconButton.d-ts.txt b/docs/design/components/core/IconButton.d-ts.txt new file mode 100644 index 0000000..d2f3545 --- /dev/null +++ b/docs/design/components/core/IconButton.d-ts.txt @@ -0,0 +1,17 @@ +/** + * Square icon-only button; label is mandatory (becomes aria-label + title). + */ +export interface IconButtonProps { + /** Lucide icon name */ + icon: string; + /** Required accessible label (also the tooltip) */ + label: string; + /** @default 'ghost' */ + variant?: 'ghost' | 'outline'; + /** @default 'md' */ + size?: 'sm' | 'md'; + disabled?: boolean; + onClick?: (e: React.MouseEvent) => void; + style?: React.CSSProperties; +} +export declare function IconButton(props: IconButtonProps): JSX.Element; diff --git a/docs/design/components/core/IconButton.js.txt b/docs/design/components/core/IconButton.js.txt new file mode 100644 index 0000000..ca7fa60 --- /dev/null +++ b/docs/design/components/core/IconButton.js.txt @@ -0,0 +1,51 @@ +import React from 'react'; +import { Icon } from './Icon'; + +const CSS = ` +.ct-iconbtn { + display: inline-flex; align-items: center; justify-content: center; + border-radius: var(--radius-2); + border: 1px solid transparent; + background: transparent; + color: var(--ink-2); + cursor: pointer; + transition: background var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out); +} +.ct-iconbtn:hover:not(:disabled) { background: var(--paper-2); color: var(--ink-1); } +.ct-iconbtn:active:not(:disabled) { background: var(--paper-3); } +.ct-iconbtn:disabled { opacity: 0.45; cursor: not-allowed; } +.ct-iconbtn--md { width: 34px; height: 34px; } +.ct-iconbtn--sm { width: 28px; height: 28px; } +.ct-iconbtn--outline { border-color: var(--line-2); background: var(--surface-card); } +.ct-iconbtn--outline:hover:not(:disabled) { background: var(--paper-2); } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-iconbtn-css')) { + const s = document.createElement('style'); s.id = 'ct-iconbtn-css'; s.textContent = CSS; + document.head.appendChild(s); + } +})(); + +export function IconButton({ + icon, + label, + variant = 'ghost', + size = 'md', + disabled = false, + style, + ...rest +}) { + return ( + + ); +} diff --git a/docs/design/components/core/IconButton.prompt.md b/docs/design/components/core/IconButton.prompt.md new file mode 100644 index 0000000..34c3c67 --- /dev/null +++ b/docs/design/components/core/IconButton.prompt.md @@ -0,0 +1,8 @@ +Icon-only square button for toolbars and row actions; `label` is required and doubles as the tooltip. + +```jsx + + +``` + +Variants: `ghost` (default), `outline`. Sizes `md` 34px / `sm` 28px. diff --git a/docs/design/components/core/Tabs.d-ts.txt b/docs/design/components/core/Tabs.d-ts.txt new file mode 100644 index 0000000..6718f1f --- /dev/null +++ b/docs/design/components/core/Tabs.d-ts.txt @@ -0,0 +1,19 @@ +/** + * Underline tabs for view switching (Board / Gantt / Dependencies). + */ +export interface TabItem { + id: string; + label: string; + /** Lucide icon name */ + icon?: string; + /** Mono count rendered after the label */ + count?: number; +} +export interface TabsProps { + items: TabItem[]; + /** id of the active tab */ + active: string; + onChange?: (id: string) => void; + style?: React.CSSProperties; +} +export declare function Tabs(props: TabsProps): JSX.Element; diff --git a/docs/design/components/core/Tabs.js.txt b/docs/design/components/core/Tabs.js.txt new file mode 100644 index 0000000..69d77d2 --- /dev/null +++ b/docs/design/components/core/Tabs.js.txt @@ -0,0 +1,54 @@ +import React from 'react'; +import { Icon } from './Icon'; + +const CSS = ` +.ct-tabs { + display: flex; gap: 2px; + border-bottom: 1px solid var(--border-hairline); +} +.ct-tab { + display: inline-flex; align-items: center; gap: 7px; + font: 500 13.5px/1 var(--font-sans); + color: var(--ink-2); + background: none; border: none; + padding: 10px 12px; + margin-bottom: -1px; + border-bottom: 2px solid transparent; + cursor: pointer; + transition: color var(--duration-fast) var(--ease-out); +} +.ct-tab:hover { color: var(--ink-1); } +.ct-tab--active { + color: var(--ink-1); + font-weight: 600; + border-bottom-color: var(--accent); +} +.ct-tab__count { font: 400 11.5px/1 var(--font-mono); color: var(--ink-3); } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-tabs-css')) { + const s = document.createElement('style'); s.id = 'ct-tabs-css'; s.textContent = CSS; + document.head.appendChild(s); + } +})(); + +export function Tabs({ items, active, onChange, style, ...rest }) { + return ( +
+ {items.map((item) => ( + + ))} +
+ ); +} diff --git a/docs/design/components/core/Tabs.prompt.md b/docs/design/components/core/Tabs.prompt.md new file mode 100644 index 0000000..292aa2f --- /dev/null +++ b/docs/design/components/core/Tabs.prompt.md @@ -0,0 +1,13 @@ +Underline tab strip for switching sibling views; active tab gets a 2px spruce underline. + +```jsx + +``` diff --git a/docs/design/components/core/Tag.d-ts.txt b/docs/design/components/core/Tag.d-ts.txt new file mode 100644 index 0000000..5a931f2 --- /dev/null +++ b/docs/design/components/core/Tag.d-ts.txt @@ -0,0 +1,12 @@ +/** + * Gitea label chip — renders label text verbatim in mono; tone derives from the label itself + * (est/[1d 2d 3d 5d 8d], p/1..4, deadline/hard). + */ +export interface TagProps { + /** Verbatim gitea label, e.g. "est/3d", "p/1", "deadline/hard" */ + label: string; + /** Renders a small remove button when provided */ + onRemove?: (e: React.MouseEvent) => void; + style?: React.CSSProperties; +} +export declare function Tag(props: TagProps): JSX.Element; diff --git a/docs/design/components/core/Tag.js.txt b/docs/design/components/core/Tag.js.txt new file mode 100644 index 0000000..194d4df --- /dev/null +++ b/docs/design/components/core/Tag.js.txt @@ -0,0 +1,54 @@ +import React from 'react'; +import { Icon } from './Icon'; + +const CSS = ` +.ct-tag { + display: inline-flex; align-items: center; gap: 5px; + font: 500 11.5px/1 var(--font-mono); + letter-spacing: var(--letter-spacing-label); + padding: 4px 9px; + border-radius: var(--radius-round); + white-space: nowrap; +} +.ct-tag--est { background: var(--label-est-bg); color: var(--label-est-text); } +.ct-tag--p1 { background: var(--danger-tint); color: var(--label-p1); } +.ct-tag--p2 { background: var(--warn-tint); color: var(--label-p2); } +.ct-tag--p3 { background: var(--info-tint); color: var(--label-p3); } +.ct-tag--p4 { background: var(--paper-2); color: var(--label-p4); } +.ct-tag--hard { background: var(--label-hard); color: var(--ink-inverse); } +.ct-tag--plain { background: var(--paper-2); color: var(--ink-2); } +.ct-tag__x { + display: inline-flex; padding: 0; margin: 0 -3px 0 0; + background: none; border: none; color: inherit; cursor: pointer; opacity: 0.6; +} +.ct-tag__x:hover { opacity: 1; } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-tag-css')) { + const s = document.createElement('style'); s.id = 'ct-tag-css'; s.textContent = CSS; + document.head.appendChild(s); + } +})(); + +function toneFor(label) { + if (label.startsWith('est/')) return 'est'; + if (label === 'p/1') return 'p1'; + if (label === 'p/2') return 'p2'; + if (label === 'p/3') return 'p3'; + if (label === 'p/4') return 'p4'; + if (label === 'deadline/hard') return 'hard'; + return 'plain'; +} + +export function Tag({ label, onRemove, style, ...rest }) { + return ( + + {label} + {onRemove ? ( + + ) : null} + + ); +} diff --git a/docs/design/components/core/Tag.prompt.md b/docs/design/components/core/Tag.prompt.md new file mode 100644 index 0000000..d268d96 --- /dev/null +++ b/docs/design/components/core/Tag.prompt.md @@ -0,0 +1,10 @@ +Gitea label chip; pass the label verbatim and the tone is derived (est/* neutral, p/1 red → p/4 gray, deadline/hard filled red). + +```jsx + + + + +``` + +Machine data stays mono and verbatim — never paraphrase a label. Use `Badge` for human state words. diff --git a/docs/design/components/core/core.card.html b/docs/design/components/core/core.card.html new file mode 100644 index 0000000..9e50fd6 --- /dev/null +++ b/docs/design/components/core/core.card.html @@ -0,0 +1,75 @@ + + + + + + + + + + + + + +
+ + + diff --git a/docs/design/components/feedback/Dialog.d-ts.txt b/docs/design/components/feedback/Dialog.d-ts.txt new file mode 100644 index 0000000..4282d06 --- /dev/null +++ b/docs/design/components/feedback/Dialog.d-ts.txt @@ -0,0 +1,14 @@ +/** + * Modal dialog — Caslon title over a double stationery rule; used for propose-approve + * moments and destructive confirms. + */ +export interface DialogProps { + open: boolean; + onClose?: () => void; + title: React.ReactNode; + /** Right-aligned action row (Buttons) */ + footer?: React.ReactNode; + children?: React.ReactNode; + style?: React.CSSProperties; +} +export declare function Dialog(props: DialogProps): JSX.Element | null; diff --git a/docs/design/components/feedback/Dialog.js.txt b/docs/design/components/feedback/Dialog.js.txt new file mode 100644 index 0000000..5060fec --- /dev/null +++ b/docs/design/components/feedback/Dialog.js.txt @@ -0,0 +1,68 @@ +import React from 'react'; +import { IconButton } from '../core/IconButton'; + +const CSS = ` +.ct-dialog-scrim { + position: fixed; inset: 0; + background: rgba(32, 38, 29, 0.4); + display: flex; align-items: center; justify-content: center; + z-index: 100; + animation: ct-dialog-fade var(--duration-base) var(--ease-out); +} +.ct-dialog { + background: var(--surface-card); + border: 1px solid var(--border-hairline); + border-radius: var(--radius-3); + box-shadow: var(--shadow-3); + width: min(480px, calc(100vw - 48px)); + max-height: calc(100vh - 96px); + display: flex; flex-direction: column; + animation: ct-dialog-rise var(--duration-base) var(--ease-out); +} +.ct-dialog__header { + display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; + padding: 20px 20px 12px; + border-bottom: 3px double var(--border-strong); + margin: 0 20px; padding-left: 0; padding-right: 0; +} +.ct-dialog__title { font: var(--text-title); color: var(--ink-1); margin: 0; } +.ct-dialog__body { padding: 16px 20px; overflow-y: auto; font: var(--text-body); color: var(--ink-1); } +.ct-dialog__footer { + display: flex; justify-content: flex-end; gap: 8px; + padding: 12px 20px 20px; +} +@keyframes ct-dialog-fade { from { opacity: 0; } } +@keyframes ct-dialog-rise { from { opacity: 0; transform: translateY(8px); } } +@media (prefers-reduced-motion: reduce) { + .ct-dialog-scrim, .ct-dialog { animation: none; } +} +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-dialog-css')) { + const s = document.createElement('style'); s.id = 'ct-dialog-css'; s.textContent = CSS; + document.head.appendChild(s); + } +})(); + +export function Dialog({ open, onClose, title, footer, children, style }) { + React.useEffect(() => { + if (!open) return; + const onKey = (e) => { if (e.key === 'Escape' && onClose) onClose(); }; + document.addEventListener('keydown', onKey); + return () => document.removeEventListener('keydown', onKey); + }, [open, onClose]); + + if (!open) return null; + return ( +
{ if (e.target === e.currentTarget && onClose) onClose(); }}> +
+
+

{title}

+ {onClose ? : null} +
+
{children}
+ {footer ?
{footer}
: null} +
+
+ ); +} diff --git a/docs/design/components/feedback/Dialog.prompt.md b/docs/design/components/feedback/Dialog.prompt.md new file mode 100644 index 0000000..8c095fe --- /dev/null +++ b/docs/design/components/feedback/Dialog.prompt.md @@ -0,0 +1,17 @@ +Modal for propose-approve and destructive confirms; the title sits over a double stationery rule. + +```jsx + + + + } +> + This deletes the milestone and unhouses 12 issues. I'd like to hear you say yes. + +``` + +Body copy may be Reginald's (serif if quoted). Escape and scrim-click close it. diff --git a/docs/design/components/feedback/Toast.d-ts.txt b/docs/design/components/feedback/Toast.d-ts.txt new file mode 100644 index 0000000..60b96e5 --- /dev/null +++ b/docs/design/components/feedback/Toast.d-ts.txt @@ -0,0 +1,12 @@ +/** + * Transient notification card. Presentational — the consumer owns positioning/stacking. + */ +export interface ToastProps { + /** @default 'info' */ + tone?: 'ok' | 'warn' | 'danger' | 'info'; + title?: React.ReactNode; + onDismiss?: () => void; + children?: React.ReactNode; + style?: React.CSSProperties; +} +export declare function Toast(props: ToastProps): JSX.Element; diff --git a/docs/design/components/feedback/Toast.js.txt b/docs/design/components/feedback/Toast.js.txt new file mode 100644 index 0000000..11d6d29 --- /dev/null +++ b/docs/design/components/feedback/Toast.js.txt @@ -0,0 +1,53 @@ +import React from 'react'; +import { Icon } from '../core/Icon'; +import { IconButton } from '../core/IconButton'; + +const CSS = ` +.ct-toast { + display: flex; align-items: flex-start; gap: 10px; + background: var(--surface-card); + border: 1px solid var(--border-hairline); + border-radius: var(--radius-2); + box-shadow: var(--shadow-2); + padding: 12px 14px; + max-width: 420px; + font: var(--text-body); + color: var(--ink-1); + animation: ct-toast-in var(--duration-slow) var(--ease-out); +} +.ct-toast__icon { display: flex; margin-top: 1px; } +.ct-toast--ok .ct-toast__icon { color: var(--ok); } +.ct-toast--warn .ct-toast__icon { color: var(--warn); } +.ct-toast--danger .ct-toast__icon { color: var(--danger); } +.ct-toast--info .ct-toast__icon { color: var(--info); } +.ct-toast__content { flex: 1; min-width: 0; } +.ct-toast__title { font: var(--text-body-strong); margin: 0 0 2px; } +@keyframes ct-toast-in { from { opacity: 0; transform: translateY(6px); } } +@media (prefers-reduced-motion: reduce) { .ct-toast { animation: none; } } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-toast-css')) { + const s = document.createElement('style'); s.id = 'ct-toast-css'; s.textContent = CSS; + document.head.appendChild(s); + } +})(); + +const TOAST_ICONS = { + ok: 'circle-check', + warn: 'triangle-alert', + danger: 'circle-alert', + info: 'info', +}; + +export function Toast({ tone = 'info', title, onDismiss, children, style, ...rest }) { + return ( +
+ +
+ {title ?

{title}

: null} + {children} +
+ {onDismiss ? : null} +
+ ); +} diff --git a/docs/design/components/feedback/Toast.prompt.md b/docs/design/components/feedback/Toast.prompt.md new file mode 100644 index 0000000..d7fd343 --- /dev/null +++ b/docs/design/components/feedback/Toast.prompt.md @@ -0,0 +1,10 @@ +Notification card for sync events and quiet agent asides; position/stacking is the consumer's job (fixed bottom-right, 12px gap). + +```jsx + + 500 issues in 3.2s. Nothing drifted. + + + I'll keep trying and say nothing more about it. + +``` diff --git a/docs/design/components/feedback/Tooltip.d-ts.txt b/docs/design/components/feedback/Tooltip.d-ts.txt new file mode 100644 index 0000000..70271d7 --- /dev/null +++ b/docs/design/components/feedback/Tooltip.d-ts.txt @@ -0,0 +1,11 @@ +/** + * Hover/focus tooltip — ink capsule, plain facts only. + */ +export interface TooltipProps { + content: React.ReactNode; + /** @default 'top' */ + side?: 'top' | 'bottom'; + children?: React.ReactNode; + style?: React.CSSProperties; +} +export declare function Tooltip(props: TooltipProps): JSX.Element; diff --git a/docs/design/components/feedback/Tooltip.js.txt b/docs/design/components/feedback/Tooltip.js.txt new file mode 100644 index 0000000..3a9a34f --- /dev/null +++ b/docs/design/components/feedback/Tooltip.js.txt @@ -0,0 +1,43 @@ +import React from 'react'; + +const CSS = ` +.ct-tooltip-wrap { position: relative; display: inline-flex; } +.ct-tooltip { + position: absolute; bottom: calc(100% + 7px); left: 50%; + transform: translateX(-50%) translateY(2px); + background: var(--ink-1); + color: var(--ink-inverse); + font: 500 12px/1.4 var(--font-sans); + padding: 5px 9px; + border-radius: var(--radius-1); + white-space: nowrap; + pointer-events: none; + opacity: 0; + transition: opacity var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out); + z-index: 50; +} +.ct-tooltip--bottom { bottom: auto; top: calc(100% + 7px); transform: translateX(-50%) translateY(-2px); } +.ct-tooltip-wrap:hover .ct-tooltip, +.ct-tooltip-wrap:focus-within .ct-tooltip { + opacity: 1; + transform: translateX(-50%) translateY(0); +} +.ct-tooltip code { font: 500 11px var(--font-mono); } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-tooltip-css')) { + const s = document.createElement('style'); s.id = 'ct-tooltip-css'; s.textContent = CSS; + document.head.appendChild(s); + } +})(); + +export function Tooltip({ content, side = 'top', children, style }) { + return ( + + {children} + + {content} + + + ); +} diff --git a/docs/design/components/feedback/Tooltip.prompt.md b/docs/design/components/feedback/Tooltip.prompt.md new file mode 100644 index 0000000..9e44d22 --- /dev/null +++ b/docs/design/components/feedback/Tooltip.prompt.md @@ -0,0 +1,8 @@ +Tooltip for icon buttons and truncated data; content is plain facts, never wit. + +```jsx + + + +opened 2026-07-01} side="bottom">… +``` diff --git a/docs/design/components/feedback/feedback.card.html b/docs/design/components/feedback/feedback.card.html new file mode 100644 index 0000000..7ce1bf1 --- /dev/null +++ b/docs/design/components/feedback/feedback.card.html @@ -0,0 +1,52 @@ + + + + + + + + + + + + + +
+ + + diff --git a/docs/design/components/forms/Checkbox.d-ts.txt b/docs/design/components/forms/Checkbox.d-ts.txt new file mode 100644 index 0000000..3127075 --- /dev/null +++ b/docs/design/components/forms/Checkbox.d-ts.txt @@ -0,0 +1,11 @@ +/** + * Checkbox with label; 16px box, spruce when checked. + */ +export interface CheckboxProps { + label?: React.ReactNode; + checked?: boolean; + onChange?: (e: React.ChangeEvent) => void; + disabled?: boolean; + style?: React.CSSProperties; +} +export declare function Checkbox(props: CheckboxProps): JSX.Element; diff --git a/docs/design/components/forms/Checkbox.js.txt b/docs/design/components/forms/Checkbox.js.txt new file mode 100644 index 0000000..b7e2da3 --- /dev/null +++ b/docs/design/components/forms/Checkbox.js.txt @@ -0,0 +1,43 @@ +import React from 'react'; +import { Icon } from '../core/Icon'; + +const CSS = ` +.ct-check { display: inline-flex; align-items: center; gap: 9px; cursor: pointer; font: var(--text-body); color: var(--ink-1); } +.ct-check--disabled { opacity: 0.5; cursor: not-allowed; } +.ct-check__input { position: absolute; opacity: 0; width: 0; height: 0; } +.ct-check__box { + width: 16px; height: 16px; flex-shrink: 0; + display: inline-flex; align-items: center; justify-content: center; + background: var(--surface-card); + border: 1px solid var(--line-2); + border-radius: var(--radius-1); + color: transparent; + transition: background var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out); +} +.ct-check:hover:not(.ct-check--disabled) .ct-check__box { border-color: var(--border-strong); } +.ct-check__input:checked + .ct-check__box { background: var(--accent); border-color: var(--accent); color: var(--ink-inverse); } +.ct-check__input:focus-visible + .ct-check__box { outline: 2px solid var(--focus-ring); outline-offset: 2px; } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-check-css')) { + const s = document.createElement('style'); s.id = 'ct-check-css'; s.textContent = CSS; + document.head.appendChild(s); + } +})(); + +export function Checkbox({ label, checked, onChange, disabled = false, style, ...rest }) { + return ( + + ); +} diff --git a/docs/design/components/forms/Checkbox.prompt.md b/docs/design/components/forms/Checkbox.prompt.md new file mode 100644 index 0000000..dabef5d --- /dev/null +++ b/docs/design/components/forms/Checkbox.prompt.md @@ -0,0 +1,5 @@ +Checkbox for multi-selects and settings toggles that read as options (use `Switch` for live on/off state). + +```jsx + setInc(e.target.checked)} /> +``` diff --git a/docs/design/components/forms/Input.d-ts.txt b/docs/design/components/forms/Input.d-ts.txt new file mode 100644 index 0000000..71c3c5c --- /dev/null +++ b/docs/design/components/forms/Input.d-ts.txt @@ -0,0 +1,21 @@ +/** + * Single-line text input with optional label, hint/error and leading icon. + */ +export interface InputProps { + label?: string; + /** Small gray helper under the field */ + hint?: string; + /** Replaces hint; turns the border madder red */ + error?: string; + /** Lucide icon name, leading */ + icon?: string; + /** Mono text for machine values (urls, tokens) @default false */ + mono?: boolean; + placeholder?: string; + value?: string; + onChange?: (e: React.ChangeEvent) => void; + disabled?: boolean; + type?: string; + style?: React.CSSProperties; +} +export declare function Input(props: InputProps): JSX.Element; diff --git a/docs/design/components/forms/Input.js.txt b/docs/design/components/forms/Input.js.txt new file mode 100644 index 0000000..9543feb --- /dev/null +++ b/docs/design/components/forms/Input.js.txt @@ -0,0 +1,54 @@ +import React from 'react'; +import { Icon } from '../core/Icon'; + +const CSS = ` +.ct-field { display: flex; flex-direction: column; gap: 6px; } +.ct-field__label { font: 600 13px/1.2 var(--font-sans); color: var(--ink-1); } +.ct-field__wrap { position: relative; display: flex; align-items: center; } +.ct-field__icon { position: absolute; left: 10px; color: var(--ink-3); pointer-events: none; display: flex; } +.ct-input { + width: 100%; height: 34px; + font: var(--text-body); + color: var(--ink-1); + background: var(--surface-card); + border: 1px solid var(--line-2); + border-radius: var(--radius-2); + padding: 0 12px; + transition: border-color var(--duration-fast) var(--ease-out); +} +.ct-input::placeholder { color: var(--ink-3); } +.ct-input:hover:not(:disabled):not(:focus) { border-color: var(--border-strong); background: var(--paper-1); } +.ct-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); } +.ct-input:disabled { opacity: 0.5; background: var(--paper-2); cursor: not-allowed; } +.ct-input--icon { padding-left: 32px; } +.ct-input--error { border-color: var(--danger); } +.ct-input--error:focus { border-color: var(--danger); box-shadow: 0 0 0 1px var(--danger); } +.ct-input--mono { font: var(--text-data); } +.ct-field__hint { font: var(--text-caption); color: var(--ink-3); margin: 0; } +.ct-field__error { font: var(--text-caption); color: var(--danger); margin: 0; } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-input-css')) { + const s = document.createElement('style'); s.id = 'ct-input-css'; s.textContent = CSS; + document.head.appendChild(s); + } +})(); + +export function Input({ label, hint, error, icon, mono = false, style, ...rest }) { + const cls = [ + 'ct-input', + icon ? 'ct-input--icon' : '', + error ? 'ct-input--error' : '', + mono ? 'ct-input--mono' : '', + ].filter(Boolean).join(' '); + return ( +
+ {label ? : null} +
+ {icon ? : null} + +
+ {error ?

{error}

: hint ?

{hint}

: null} +
+ ); +} diff --git a/docs/design/components/forms/Input.prompt.md b/docs/design/components/forms/Input.prompt.md new file mode 100644 index 0000000..b5c05e7 --- /dev/null +++ b/docs/design/components/forms/Input.prompt.md @@ -0,0 +1,8 @@ +Text input; focus is a spruce border, errors are stated plainly. + +```jsx + + +``` + +`mono` for machine values (URLs, tokens, label strings). Error copy may carry the wit; the field itself stays sober. diff --git a/docs/design/components/forms/Radio.d-ts.txt b/docs/design/components/forms/Radio.d-ts.txt new file mode 100644 index 0000000..b9e9244 --- /dev/null +++ b/docs/design/components/forms/Radio.d-ts.txt @@ -0,0 +1,13 @@ +/** + * Radio button with label; group by `name`. + */ +export interface RadioProps { + label?: React.ReactNode; + checked?: boolean; + onChange?: (e: React.ChangeEvent) => void; + name?: string; + value?: string; + disabled?: boolean; + style?: React.CSSProperties; +} +export declare function Radio(props: RadioProps): JSX.Element; diff --git a/docs/design/components/forms/Radio.js.txt b/docs/design/components/forms/Radio.js.txt new file mode 100644 index 0000000..6f7c037 --- /dev/null +++ b/docs/design/components/forms/Radio.js.txt @@ -0,0 +1,50 @@ +import React from 'react'; + +const CSS = ` +.ct-radio { display: inline-flex; align-items: center; gap: 9px; cursor: pointer; font: var(--text-body); color: var(--ink-1); } +.ct-radio--disabled { opacity: 0.5; cursor: not-allowed; } +.ct-radio__input { position: absolute; opacity: 0; width: 0; height: 0; } +.ct-radio__dot { + width: 16px; height: 16px; flex-shrink: 0; + border: 1px solid var(--line-2); + border-radius: 50%; + background: var(--surface-card); + display: inline-flex; align-items: center; justify-content: center; + transition: border-color var(--duration-fast) var(--ease-out); +} +.ct-radio__dot::after { + content: ''; + width: 8px; height: 8px; border-radius: 50%; + background: transparent; + transition: background var(--duration-fast) var(--ease-out); +} +.ct-radio:hover:not(.ct-radio--disabled) .ct-radio__dot { border-color: var(--border-strong); } +.ct-radio__input:checked + .ct-radio__dot { border-color: var(--accent); } +.ct-radio__input:checked + .ct-radio__dot::after { background: var(--accent); } +.ct-radio__input:focus-visible + .ct-radio__dot { outline: 2px solid var(--focus-ring); outline-offset: 2px; } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-radio-css')) { + const s = document.createElement('style'); s.id = 'ct-radio-css'; s.textContent = CSS; + document.head.appendChild(s); + } +})(); + +export function Radio({ label, checked, onChange, name, value, disabled = false, style, ...rest }) { + return ( + + ); +} diff --git a/docs/design/components/forms/Radio.prompt.md b/docs/design/components/forms/Radio.prompt.md new file mode 100644 index 0000000..faba890 --- /dev/null +++ b/docs/design/components/forms/Radio.prompt.md @@ -0,0 +1,6 @@ +Radio for one-of choices ("deadline: hard or soft?" — Reginald asks at milestone creation). + +```jsx + + +``` diff --git a/docs/design/components/forms/Select.d-ts.txt b/docs/design/components/forms/Select.d-ts.txt new file mode 100644 index 0000000..3ec263b --- /dev/null +++ b/docs/design/components/forms/Select.d-ts.txt @@ -0,0 +1,16 @@ +/** + * Styled native select with Lucide chevron. + */ +export interface SelectOption { + value: string; + label: string; +} +export interface SelectProps { + label?: string; + options: SelectOption[]; + value?: string; + onChange?: (e: React.ChangeEvent) => void; + disabled?: boolean; + style?: React.CSSProperties; +} +export declare function Select(props: SelectProps): JSX.Element; diff --git a/docs/design/components/forms/Select.js.txt b/docs/design/components/forms/Select.js.txt new file mode 100644 index 0000000..bd4644a --- /dev/null +++ b/docs/design/components/forms/Select.js.txt @@ -0,0 +1,46 @@ +import React from 'react'; +import { Icon } from '../core/Icon'; + +const CSS = ` +.ct-select-field { display: flex; flex-direction: column; gap: 6px; } +.ct-select-field__label { font: 600 13px/1.2 var(--font-sans); color: var(--ink-1); } +.ct-select__wrap { position: relative; display: flex; align-items: center; } +.ct-select { + width: 100%; height: 34px; + font: var(--text-body); + color: var(--ink-1); + background: var(--surface-card); + border: 1px solid var(--line-2); + border-radius: var(--radius-2); + padding: 0 30px 0 12px; + appearance: none; + cursor: pointer; + transition: border-color var(--duration-fast) var(--ease-out); +} +.ct-select:hover:not(:disabled):not(:focus) { border-color: var(--border-strong); } +.ct-select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); } +.ct-select:disabled { opacity: 0.5; cursor: not-allowed; } +.ct-select__chevron { position: absolute; right: 10px; color: var(--ink-3); pointer-events: none; display: flex; } +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-select-css')) { + const s = document.createElement('style'); s.id = 'ct-select-css'; s.textContent = CSS; + document.head.appendChild(s); + } +})(); + +export function Select({ label, options, style, ...rest }) { + return ( +
+ {label ? : null} +
+ + +
+
+ ); +} diff --git a/docs/design/components/forms/Select.prompt.md b/docs/design/components/forms/Select.prompt.md new file mode 100644 index 0000000..4440062 --- /dev/null +++ b/docs/design/components/forms/Select.prompt.md @@ -0,0 +1,14 @@ +Native select, restyled; use for small closed sets (estimate labels, priority, model role). + +```jsx + + + {label ? {label} : null} + + ); +} diff --git a/docs/design/components/forms/Switch.prompt.md b/docs/design/components/forms/Switch.prompt.md new file mode 100644 index 0000000..be381a1 --- /dev/null +++ b/docs/design/components/forms/Switch.prompt.md @@ -0,0 +1,5 @@ +Switch for live on/off state (webhooks, morning service, theme). Motion is settled — 200ms ease-out slide. + +```jsx + setOn(e.target.checked)} /> +``` diff --git a/docs/design/components/forms/forms.card.html b/docs/design/components/forms/forms.card.html new file mode 100644 index 0000000..e5cec78 --- /dev/null +++ b/docs/design/components/forms/forms.card.html @@ -0,0 +1,48 @@ + + + + + + + + + + + + + +
+ + + diff --git a/docs/design/design_system_readme.md b/docs/design/design_system_readme.md new file mode 100644 index 0000000..704ff24 --- /dev/null +++ b/docs/design/design_system_readme.md @@ -0,0 +1,84 @@ +# CommiTea Design System + +**CommiTea** is an AI-project-manager built on Gitea: a lightweight Electron app (one window, React + Tailwind) where a local-ish LLM agent captures work via interview, maps capacity, and forecasts deadlines with Monte Carlo honesty. Gitea holds human-authored intent (issues, milestones, labels); a sidecar holds machine-derived state (forecasts, calibration). The agent talks; a deterministic scheduler does the math. + +**Sources:** `uploads/commitea.md` — the product plan (concept, architecture decisions, UI intents, phases) — and `uploads/commitea.jpeg`, the app-icon logo (green tea cup on slate-teal). No Figma or codebase was provided; this system was designed from scratch against the brand brief: *"High-class British tea drinking experience, with intelligent professionalism — a 140-IQ assistant. The wit in the copy is what reminds you you're working professionally and playfully."* + +**Product surfaces** (from the plan): Focus card (Now/Next/Later), burn-up chart with forecast cone, runway view, kanban + dependency drill-ins, and agent chat (chat = write-path, UI = read-path). + +**Logo:** the provided app icon lives at `assets/logo.jpeg` (original, white margin) and `assets/logo-icon.png` (cropped, rounded). In UI chrome, pair the icon at 24px with the typographic wordmark — "CommiTea" in Libre Caslon Display. Never redraw the mark. + +--- + +## CONTENT FUNDAMENTALS + +The agent is **Reginald** — a brilliant, unflappable English gentleman of a project manager. Dry wit, never silly. Wit level ~7/10: almost every surface has one line with a raised eyebrow, and everything else is crisp and factual. He is "Reginald" in the UI and in copy — never "the AI", never "the assistant". + +**Rules** + +- **Reginald speaks in first person, addresses you as "you".** He has opinions and states them: "I'd take the parser bug first; it blocks three others." +- **Numbers are sacred, phrasing is warm.** Forecasts are always ranges, never point dates: "80% chance this lands Mar 3–12." Reginald never hedges the math, only decorates it: "The cone has narrowed. I'm quietly pleased." +- **Wit lives in complete sentences,** usually the last one. Never in buttons, labels, or data. Buttons are plain verbs: "Approve", "Brew plan", "Defer". +- **Tea vocabulary is a seasoning, not a theme park.** Sanctioned terms: *brew* (generate/plan), *steep* (work in progress), *service* (the daily standup — "Morning service"), *the pot* (backlog). Use at most one per screen. Never "tea-rrific", never puns on "oolong". +- **Sentence case everywhere** — titles, buttons, headers. No Title Case, no ALL CAPS except tiny overline labels. +- **No emoji. Ever.** +- **No exclamation marks** except in genuine celebration (milestone closed early). +- Machine data renders in mono, verbatim: `est/3d`, `p/2`, `deadline/hard`, `#142`. + +**Specimen copy** + +- Empty backlog: "The pot is empty. Tell me what you're planning and I'll draw up the tickets." +- Morning standup: "Morning service. Two things drifted overnight; one needs your opinion." +- Consequence diff: "Done — X ships today. Milestone Beta moves +6 days. Shall I make it so?" +- Blocked nag: "#87 has been steeping for four days. It blocks #91 and #92. Worth a look." +- Forecast: "80% this lands Mar 3–12. The estimate history says you're optimists — I've adjusted." +- Error: "Gitea isn't answering. I'll keep trying and say nothing more about it." +- Destructive confirm: "This deletes the milestone and unhouses 12 issues. I'd like to hear you say yes." + +--- + +## VISUAL FOUNDATIONS + +The aesthetic: **fine stationery meets instrument panel, poured as green tea.** Cool porcelain, slate-green ink, a hairline of jade. The interface should feel like a beautifully typeset letter that happens to compute Monte Carlo forecasts. + +- **Color.** Cool porcelain surfaces (`--paper-0/1/2`, green-tinted whites), slate-green ink scale (`--ink-1/2/3`), one brand green (spruce — the logo's slate-teal, `--accent`), one gem (jade `--jade`, the tea's mint — used only in hairlines and small accents, never large fills). Status colors are muted (moss, oolong amber, madder red, wedgwood blue). Two themes: light "morning service" (default) and dark "evening service" via `[data-theme="dark"]`. +- **Type.** Four voices, strictly cast: **Libre Caslon Display** for headlines and hero numerals; **Libre Caslon Text (upright)** exclusively for Reginald's speech (if it's serif body text, Reginald said it — italics are reserved for occasional emphasis inside his sentences); **Instrument Sans** for all UI; **IBM Plex Mono** for machine data — labels, estimates, dates, issue ids, counts. Never let voices bleed: no serif UI copy, no sans forecasts. +- **Backgrounds.** Flat paper colors. No gradients, no textures, no imagery. Depth comes from paper-on-paper (card on app bg) plus hairlines. +- **Borders & rules.** 1px hairlines (`--line-1`) everywhere; a `3px double` rule (`--rule-double`) tops major sections like fine stationery. Key cards take a 2px inset jade top rule (`--shadow-jade-line`). +- **Cards.** `--surface-card` on `--surface-app`, 1px hairline border, `--radius-3` (10px), `--shadow-1`. Elevation is whispered, not shouted; `--shadow-3` is reserved for dialogs/popovers. +- **Corner radii.** Crisp: 4/6/10px. Pills only for chips and status dots. Nothing bubbly. +- **Hover states.** Surfaces: one porcelain step darker (`--paper-2`). Buttons: darker fill (`--accent-hover`). Links: jade underline appears. Never opacity fades on text. +- **Press states.** One step darker again (`--accent-pressed`); no shrink transforms. +- **Focus.** 2px `--focus-ring` outline, 2px offset, everywhere. +- **Motion.** Settled and gentlemanly: 120–320ms, `--ease-out`, fades and small translates (4–8px). No bounces, no springs, no infinite loops. The forecast cone may draw itself in once (~320ms). +- **Transparency & blur.** Almost never. Only dialog scrims (`rgba` ink at 40%). No glassmorphism. +- **Data visualization.** The cone is geometry, not decoration: filled band `--cone-fill`, edge `--cone-line`, actuals in ink. Ahead/behind reads as position, not color alone. +- **Layout.** Generous headers (Caslon breathes), compact data rows (dev-tool density in tables/boards, 32–36px rows). Fixed left rail navigation in-app; content column max ~1120px on wide screens. +- **Imagery.** None beyond the logo. No stock photos, no illustrations. The typography is the decoration. + +--- + +## ICONOGRAPHY + +- **System:** [Lucide](https://lucide.dev) at 1.5px stroke — copied locally into `assets/icons/*.svg` (69 icons, lucide-static v0.462.0). Use these files; do not hand-draw SVGs. +- **Usage:** 16px inline with text, 18–20px in buttons/nav. Stroke inherits `currentColor`. Icons always accompany a label except in `IconButton` (which requires a tooltip/aria-label). +- **Domain mapping:** issues `circle-dot`, milestones `milestone`, board `square-kanban`, forecast `chart-line`, gantt `chart-no-axes-gantt`, deps `network`, agent `sparkles`, standup `coffee`, capacity `gauge`, directives `flag`, git events `git-branch/git-commit-horizontal/git-pull-request/git-merge`. +- **No emoji, no unicode-chars-as-icons.** The only glyph liberty: mono `·` as a list separator and `—` in agent prose. +- **No icon font.** Inline the SVG or reference the file. + +--- + +## INDEX + +- `styles.css` — global entry; imports everything under `tokens/` +- `tokens/` — `colors.css`, `typography.css`, `spacing.css`, `fonts.css`, `base.css` +- `assets/fonts/` — self-hosted woff2 (Libre Caslon Display/Text, Instrument Sans, IBM Plex Mono) +- `assets/icons/` — Lucide SVGs (local copies); `assets/logo.jpeg` + `assets/logo-icon.png` — brand mark +- `guidelines/` — foundation specimen cards (Design System tab) +- `components/core/` — Button, IconButton, Badge, Tag, Card, Tabs +- `components/forms/` — Input, Select, Checkbox, Radio, Switch +- `components/feedback/` — Dialog, Toast, Tooltip +- `ui_kits/app/` — CommiTea Electron app recreation (Focus, Board, Agent chat, Runway) +- `SKILL.md` — agent-facing entry point + +**Intentional additions:** `Icon` (wrapper that inlines local Lucide SVGs — needed because icons ship as files); `Tag` doubles as the gitea label chip (`est/*`, `p/*`, `deadline/hard`). diff --git a/docs/design/styles.css b/docs/design/styles.css new file mode 100644 index 0000000..bb57c82 --- /dev/null +++ b/docs/design/styles.css @@ -0,0 +1,5 @@ +@import "tokens/fonts.css"; +@import "tokens/colors.css"; +@import "tokens/typography.css"; +@import "tokens/spacing.css"; +@import "tokens/base.css"; diff --git a/docs/design/tokens/base.css b/docs/design/tokens/base.css new file mode 100644 index 0000000..e491680 --- /dev/null +++ b/docs/design/tokens/base.css @@ -0,0 +1,37 @@ +/* CommiTea base element styles. */ + +* { box-sizing: border-box; } + +body { + margin: 0; + font: var(--text-body); + color: var(--text-body); + background: var(--surface-app); + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +a { + color: var(--accent-text); + text-decoration: none; + border-bottom: 1px solid var(--line-2); + transition: border-color var(--duration-fast) var(--ease-out); +} +a:hover { + color: var(--accent-hover); + border-bottom-color: var(--jade); +} + +::selection { + background: var(--spruce-2); + color: var(--ink-1); +} + +code, kbd { + font: var(--text-data); +} + +:focus-visible { + outline: 2px solid var(--focus-ring); + outline-offset: 2px; +} diff --git a/docs/design/tokens/colors.css b/docs/design/tokens/colors.css new file mode 100644 index 0000000..1d237cf --- /dev/null +++ b/docs/design/tokens/colors.css @@ -0,0 +1,137 @@ +/* CommiTea color system — "Green tea service" + Cool porcelain surfaces, slate-green ink, jade accent (from the cup itself). + Light is default (morning service). Dark scope: [data-theme="dark"] (evening service). */ + +:root { + /* ---- base: porcelain (cool green-white surfaces) ---- */ + --paper-0: #F0F4F0; /* app background */ + --paper-1: #FAFCF9; /* raised card */ + --paper-2: #E4EBE4; /* inset wells, rails */ + --paper-3: #D7E1D8; /* pressed / deep inset */ + + /* ---- base: ink (slate-green text scale) ---- */ + --ink-1: #1D2522; /* primary text */ + --ink-2: #51605A; /* secondary text */ + --ink-3: #84928B; /* muted / placeholder */ + --ink-inverse: #EFF5F0; + + /* ---- base: lines ---- */ + --line-1: #D7DFD7; /* hairline */ + --line-2: #BACBBE; /* strong rule */ + + /* ---- base: spruce (brand green — the logo's slate-teal) ---- */ + --spruce-9: #16322A; + --spruce-8: #1D4238; + --spruce-7: #275546; /* brand anchor */ + --spruce-6: #3A7260; + --spruce-5: #57937C; + --spruce-3: #A3CFBB; + --spruce-2: #CDE5D7; + --spruce-1: #E3F0E8; + + /* ---- base: jade (mint accent — the tea itself) ---- */ + --jade-7: #2E7A57; + --jade-6: #46996F; + --jade-5: #66B389; + --jade-3: #A8DDBE; + --jade-1: #E0F4E7; + + /* ---- semantic: surfaces & text ---- */ + --surface-app: var(--paper-0); + --surface-card: var(--paper-1); + --surface-inset: var(--paper-2); + --surface-raised: var(--paper-1); + --text-body: var(--ink-1); + --text-secondary: var(--ink-2); + --text-muted: var(--ink-3); + --text-on-accent: var(--ink-inverse); + --border-hairline: var(--line-1); + --border-strong: var(--line-2); + + /* ---- semantic: interactive ---- */ + --accent: var(--spruce-7); + --accent-hover: #1F4A3C; + --accent-pressed: #173B30; + --accent-tint: var(--spruce-1); + --accent-text: var(--spruce-7); + --jade: var(--jade-6); + --jade-tint: var(--jade-1); + --focus-ring: #57937C; + + /* ---- semantic: status ---- */ + --ok: #2F7D53; + --ok-tint: #DFEEE3; + --warn: #96772A; + --warn-tint: #EFE9D2; + --danger: #A84632; + --danger-tint: #F2E0DA; + --info: #43758F; + --info-tint: #DFE9EC; + + /* ---- domain: label chips (gitea label sets) ---- */ + --label-est-bg: var(--paper-2); + --label-est-text: var(--ink-2); + --label-p1: #A84632; + --label-p2: #96772A; + --label-p3: #43758F; + --label-p4: #84928B; + --label-hard: #A84632; + + /* ---- domain: forecast ---- */ + --cone-fill: rgba(102, 179, 137, 0.18); + --cone-line: #46996F; + --cone-actual: var(--ink-1); +} + +[data-theme="dark"] { + /* evening service */ + --paper-0: #121C18; + --paper-1: #182420; + --paper-2: #1F2E28; + --paper-3: #283A32; + + --ink-1: #E4EEE7; + --ink-2: #9FB2A8; + --ink-3: #66796F; + --ink-inverse: #EFF5F0; + + --line-1: #263630; + --line-2: #35493F; + + --spruce-1: #1E332B; + --spruce-2: #27443A; + --spruce-3: #3A6353; + + --jade-1: #1F3A2E; + --jade-3: #35624A; + + --accent: #3A7260; + --accent-hover: #448069; + --accent-pressed: #315F51; + --accent-tint: #1E332B; + --accent-text: #8CC7AC; + --jade: #6FC694; + --jade-tint: #1F3A2E; + --focus-ring: #57937C; + + --ok: #74B992; + --ok-tint: #1F3529; + --warn: #C0A45B; + --warn-tint: #33301F; + --danger: #C77B62; + --danger-tint: #392823; + --info: #7FA6BC; + --info-tint: #223038; + + --label-est-bg: var(--paper-3); + --label-est-text: var(--ink-2); + --label-p1: #C77B62; + --label-p2: #C0A45B; + --label-p3: #7FA6BC; + --label-p4: #66796F; + --label-hard: #C77B62; + + --cone-fill: rgba(111, 198, 148, 0.14); + --cone-line: #6FC694; + --cone-actual: #E4EEE7; +} diff --git a/docs/design/tokens/fonts.css b/docs/design/tokens/fonts.css new file mode 100644 index 0000000..56074d5 --- /dev/null +++ b/docs/design/tokens/fonts.css @@ -0,0 +1,65 @@ +/* CommiTea webfonts — self-hosted, latin subsets */ + +@font-face { + font-family: 'Libre Caslon Display'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../assets/fonts/caslon-display-normal-400.woff2) format('woff2'); +} +@font-face { + font-family: 'Libre Caslon Text'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../assets/fonts/caslon-text-normal-400.woff2) format('woff2'); +} +@font-face { + font-family: 'Libre Caslon Text'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(../assets/fonts/caslon-text-italic-400.woff2) format('woff2'); +} +@font-face { + font-family: 'Libre Caslon Text'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(../assets/fonts/caslon-text-normal-700.woff2) format('woff2'); +} +@font-face { + font-family: 'Instrument Sans'; + font-style: normal; + font-weight: 400 700; + font-display: swap; + src: url(../assets/fonts/instrument-sans-normal-400-700.woff2) format('woff2'); +} +@font-face { + font-family: 'Instrument Sans'; + font-style: italic; + font-weight: 400 700; + font-display: swap; + src: url(../assets/fonts/instrument-sans-italic-400-700.woff2) format('woff2'); +} +@font-face { + font-family: 'IBM Plex Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../assets/fonts/plex-mono-normal-400.woff2) format('woff2'); +} +@font-face { + font-family: 'IBM Plex Mono'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(../assets/fonts/plex-mono-normal-500.woff2) format('woff2'); +} +@font-face { + font-family: 'IBM Plex Mono'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(../assets/fonts/plex-mono-normal-600.woff2) format('woff2'); +} diff --git a/docs/design/tokens/spacing.css b/docs/design/tokens/spacing.css new file mode 100644 index 0000000..0bc88ca --- /dev/null +++ b/docs/design/tokens/spacing.css @@ -0,0 +1,44 @@ +/* CommiTea spacing, radius, shadow, border & motion tokens. */ + +:root { + /* spacing — 4px base */ + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + --space-7: 32px; + --space-8: 40px; + --space-9: 48px; + --space-10: 64px; + + /* radii — crisp, not bubbly */ + --radius-1: 4px; /* chips, inputs' inner elements */ + --radius-2: 6px; /* buttons, inputs */ + --radius-3: 10px; /* cards, dialogs */ + --radius-round: 999px; + + /* borders */ + --border-w: 1px; + --rule-double: 3px double var(--border-strong); /* fine-stationery section rule */ + + /* shadows — cool, restrained */ + --shadow-1: 0 1px 2px rgba(20, 34, 28, 0.06); + --shadow-2: 0 2px 8px rgba(20, 34, 28, 0.08), 0 1px 2px rgba(20, 34, 28, 0.05); + --shadow-3: 0 12px 32px rgba(20, 34, 28, 0.14), 0 2px 8px rgba(20, 34, 28, 0.07); + --shadow-jade-line: inset 0 2px 0 var(--jade); /* jade top rule on key cards */ + + /* motion — settled, never bouncy */ + --ease-out: cubic-bezier(0.25, 0.6, 0.3, 1); /* @kind other */ + --ease-in-out: cubic-bezier(0.6, 0, 0.3, 1); /* @kind other */ + --duration-fast: 120ms; /* @kind other */ + --duration-base: 200ms; /* @kind other */ + --duration-slow: 320ms; /* @kind other */ +} + +[data-theme="dark"] { + --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.25); + --shadow-2: 0 2px 8px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2); + --shadow-3: 0 12px 32px rgba(0, 0, 0, 0.45), 0 2px 8px rgba(0, 0, 0, 0.25); +} diff --git a/docs/design/tokens/typography.css b/docs/design/tokens/typography.css new file mode 100644 index 0000000..2556118 --- /dev/null +++ b/docs/design/tokens/typography.css @@ -0,0 +1,34 @@ +/* CommiTea typography tokens. + Voices: Display serif (headlines, big numerals) · Reginald's voice (Caslon Text, upright) + · Body sans (UI) · Mono (data: labels, estimates, dates, ids). */ + +:root { + --font-serif-display: 'Libre Caslon Display', 'Libre Caslon Text', Georgia, serif; + --font-serif-text: 'Libre Caslon Text', Georgia, serif; + --font-sans: 'Instrument Sans', -apple-system, 'Segoe UI', sans-serif; + --font-mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', monospace; + + /* type scale */ + --text-hero: 400 48px/1.08 var(--font-serif-display); /* big forecast numerals */ + --text-display: 400 34px/1.15 var(--font-serif-display); /* page titles */ + --text-title: 400 24px/1.2 var(--font-serif-display); /* card titles */ + --text-agent: 400 16px/1.55 var(--font-serif-text); /* Reginald speaks — serif, upright */ + --text-agent-lg: 400 20px/1.5 var(--font-serif-text); + --text-body: 400 14px/1.5 var(--font-sans); + --text-body-strong: 600 14px/1.5 var(--font-sans); + --text-small: 400 13px/1.45 var(--font-sans); + --text-caption: 400 12px/1.4 var(--font-sans); + --text-data: 400 13px/1.4 var(--font-mono); /* dates, counts */ + --text-label: 500 11.5px/1 var(--font-mono); /* chips: est/2d, p/1 */ + --text-overline: 600 11px/1.2 var(--font-sans); /* + letter-spacing-wide */ + + /* tracking */ + --letter-spacing-wide: 0.08em; /* overlines, uppercase */ + --letter-spacing-label: 0.02em; /* mono chips */ + + /* weights (sans is variable 400–700) */ + --weight-regular: 400; + --weight-medium: 500; + --weight-semibold: 600; + --weight-bold: 700; +} diff --git a/docs/design/ui_kits/app/BoardScreen.js.txt b/docs/design/ui_kits/app/BoardScreen.js.txt new file mode 100644 index 0000000..06fbfde --- /dev/null +++ b/docs/design/ui_kits/app/BoardScreen.js.txt @@ -0,0 +1,80 @@ +// Board — kanban over inferred lifecycle, with Gantt/Dependencies stubs +function BoardScreen({ onOpenIssue }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Card, Tag, Badge, Tabs, IconButton, Input, Icon } = DS; + const d = window.CT_DATA; + const [tab, setTab] = React.useState('board'); + const [query, setQuery] = React.useState(''); + const q = query.trim().toLowerCase(); + const filtered = d.columns.map((c) => ({ ...c, issues: q ? c.issues.filter((i) => (i.title + ' #' + i.id).toLowerCase().includes(q)) : c.issues })); + const anyMatch = filtered.some((c) => c.issues.length > 0); + const openCount = d.columns.reduce((n, c) => n + (c.id === 'done' ? 0 : c.issues.length), 0); + + const IssueCard = ({ issue }) => ( +
onOpenIssue(issue)} + style={{ + background: 'var(--surface-card)', border: '1px solid var(--line-1)', + borderRadius: 'var(--radius-2)', padding: '10px 12px', cursor: 'pointer', + boxShadow: 'var(--shadow-1)', display: 'flex', flexDirection: 'column', gap: 8, + }} + onMouseEnter={(e) => { e.currentTarget.style.borderColor = 'var(--line-2)'; }} + onMouseLeave={(e) => { e.currentTarget.style.borderColor = 'var(--line-1)'; }} + > +
{issue.title}
+
+ #{issue.id} + {issue.labels.map((l) => )} + {issue.days ? {issue.days} : null} + {issue.pr ? {issue.pr} : null} + {issue.who} +
+
+ ); + + return ( +
+
+

The pot

+
setQuery(e.target.value)} />
+
+ + {tab === 'board' ? ( + anyMatch ? ( +
+ {filtered.map((col) => ( +
+
+ {col.label} + {col.issues.length} +
+ {col.issues.map((i) => )} +
+ ))} +
+ ) : ( +
+ n + c.issues.length, 0)} issues; none of them answer to “${query.trim()}”.`} /> +
+ ) + ) : tab === 'deps' ? ( + + ) : ( + + )} +
+ ); +} +Object.assign(window, { BoardScreen }); diff --git a/docs/design/ui_kits/app/CalibrationScreen.js.txt b/docs/design/ui_kits/app/CalibrationScreen.js.txt new file mode 100644 index 0000000..9b6afd5 --- /dev/null +++ b/docs/design/ui_kits/app/CalibrationScreen.js.txt @@ -0,0 +1,128 @@ +// Calibration report — estimate-vs-actual evidence behind the cones +function CalibrationScreen({ onBack }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Card, Badge, Icon } = DS; + const c = window.CT_DATA.calibration; + + // scatter chart geometry + const W = 420, H = 300, pad = { l: 36, r: 16, t: 14, b: 30 }; + const maxD = 9; + const X = (d) => pad.l + (d / maxD) * (W - pad.l - pad.r); + const Y = (d) => H - pad.b - (d / maxD) * (H - pad.t - pad.b); + + const BiasBar = ({ bias }) => { + if (bias == null) return n too small; + return ( +
+
+
+
15 ? 'var(--warn)' : 'var(--ok)', borderRadius: '0 3px 3px 0', opacity: 0.75, + }}>
+
+ 15 ? 'var(--warn)' : 'var(--ok)', width: 42, textAlign: 'right' }}>+{bias}% +
+ ); + }; + + return ( +
+
+ +
+
+

Calibration

+

{c.n} closed issues with estimates · evidence, not opinion

+
+ curve active · n ≥ 20 +
+
+ +
+ {/* scatter */} + + + {[1, 3, 5, 8].map((d) => ( + + + {d}d + + {d}d + + ))} + {/* perfect line */} + + honest + {/* fit */} + + you · ×{c.fit} + {/* points */} + {c.scatter.map(([e, a], i) => ( + + ))} + +

estimated (x) vs actual days (y) · actuals inferred from git events, never tracked

+
+ +
+ {/* per-label bias */} + +
+ {c.labels.map((r, i) => ( +
+ {r.label} + n={r.n} · {r.median} + +
+ ))} +
+
+ + {/* per-person */} + +
+ {c.people.map((p, i) => ( +
+ {p.who.split(' ').map((w) => w[0]).join('')} +
+ {p.who} + · n={p.n} · {p.note} +
+ 15 ? 'var(--warn)' : 'var(--ok)' }}>+{p.bias}% +
+ ))} +
+
+ + {/* effect on forecasts */} + +
+ {c.effect.raw} + + {c.effect.banded} +
+

+ You are not bad at estimating; you are optimistic in a very stable way. Stable, I can work with. +

+
+
+
+
+ ); +} +Object.assign(window, { CalibrationScreen }); diff --git a/docs/design/ui_kits/app/CaptureScreen.js.txt b/docs/design/ui_kits/app/CaptureScreen.js.txt new file mode 100644 index 0000000..436de46 --- /dev/null +++ b/docs/design/ui_kits/app/CaptureScreen.js.txt @@ -0,0 +1,185 @@ +// Capture interview — braindump → interview → approved ticket set (< 2 min) +function CaptureScreen({ onDone }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Button, Card, Tag, Badge, Select, Icon } = DS; + + const [stage, setStage] = React.useState('dump'); // dump | interview | review | filed + const [dump, setDump] = React.useState( + 'auth is flaky \u2014 token refresh dies silently, sometimes session storage goes stale. ' + + 'also the webhook debounce thing keeps double-firing. and we owe docs for auth setup' + ); + const [qi, setQi] = React.useState(0); + const [log, setLog] = React.useState([]); + const [split, setSplit] = React.useState(null); + const [webEst, setWebEst] = React.useState(null); + const [secs, setSecs] = React.useState(0); + + const running = stage === 'interview' || stage === 'review'; + React.useEffect(() => { + if (!running) return; + const t = setInterval(() => setSecs((s) => s + 1), 1000); + return () => clearInterval(t); + }, [running]); + const clock = `${Math.floor(secs / 60)}:${String(secs % 60).padStart(2, '0')}`; + + const QUESTIONS = [ + { q: 'The auth work \u2014 one ticket, or shall I split token refresh from session storage? They fail differently.', + chips: ['One ticket', 'Split them'], set: (a) => setSplit(a === 'Split them') }, + { q: 'The webhook double-fire \u2014 how long? I should mention your "quick" has averaged two days.', + chips: ['est/1d', 'est/2d', 'est/3d'], set: (a) => setWebEst(a) }, + { q: 'Milestone Beta, I presume? It has room, provided the auth work stays under four days.', + chips: ['Beta', 'New milestone'], set: () => {} }, + ]; + + const answer = (a) => { + QUESTIONS[qi].set(a); + setLog((l) => [...l, { q: QUESTIONS[qi].q, a }]); + if (qi + 1 < QUESTIONS.length) setQi(qi + 1); + else setStage('review'); + }; + + // draft tickets build as the interview progresses + const tickets = []; + if (split === true) { + tickets.push({ title: 'Token refresh: retry with backoff', est: 'est/2d', p: 'p/2' }); + tickets.push({ title: 'Session storage: stale reads on wake', est: 'est/1d', p: 'p/3' }); + } else if (split === false) { + tickets.push({ title: 'Auth: token refresh + session storage', est: 'est/3d', p: 'p/2' }); + } + if (webEst) tickets.push({ title: 'Webhook debounce: double-fire guard', est: webEst, p: 'p/1', dep: 'blocked by auth work' }); + if (stage === 'review' || stage === 'filed') { + tickets.push({ title: 'Docs: auth setup guide', est: 'est/1d', p: 'p/4', byReginald: true }); + } + const totalDays = tickets.reduce((n, t) => n + parseInt(t.est.replace('est/', '')), 0); + + const estOptions = ['est/1d', 'est/2d', 'est/3d', 'est/5d', 'est/8d'].map((v) => ({ value: v, label: v })); + const pOptions = ['p/1', 'p/2', 'p/3', 'p/4'].map((v) => ({ value: v, label: v })); + + const Tray = ({ editable }) => ( + 1 ? 's' : ''}` : 'Empty, for now'} flush> +
+ {tickets.length === 0 ? ( +

+ Tickets appear here as we talk. +

+ ) : tickets.map((t, i) => ( +
+
{t.title}
+
+ {editable ? ( + <> + + + ) : ( + <> + + + + )} + {t.dep ? {t.dep} : null} + {t.byReginald ? added by Reginald : null} +
+
+ ))} +
+
+ ); + + return ( +
+
+
+

Capture

+

braindump → approved tickets

+
+ {stage !== 'dump' ? ( +
+
120 ? 'var(--warn)' : 'var(--ink-1)' }}>{clock}
+
budget 2:00
+
+ ) : null} +
+ + {stage === 'dump' ? ( + + +

+ Sentences, fragments, grievances — all welcome. I'll sort it into tickets and only ask what I can't infer. +

+ +
+ ) : null} + + {stage === 'interview' ? ( +
+ +
+ {log.map((e, i) => ( +
+ {e.q} + {e.a} +
+ ))} +

{QUESTIONS[qi].q}

+
+ {QUESTIONS[qi].chips.map((c) => ( + + ))} +
+
+
+ +
+ ) : null} + + {stage === 'review' ? ( +
+ + + + }> +

+ Beta's 80% window moves Mar 3–12 → Mar 5–14. Capacity absorbs the rest. +

+

+ I added the docs ticket you mentioned and wired the dependency. Shall I make it so? +

+
+ +
+ ) : null} + + {stage === 'filed' ? ( + +
+ + Filed + +

+ {tickets.length} issues opened in gitea with est/* and p/* labels — nothing else touched. +

+

+ Elapsed {clock} — under budget. No bot comments, no synthetic issues; your repo remains yours. +

+ +
+
+ ) : null} +
+ ); +} +Object.assign(window, { CaptureScreen }); diff --git a/docs/design/ui_kits/app/Chart.js.txt b/docs/design/ui_kits/app/Chart.js.txt new file mode 100644 index 0000000..35b3f6a --- /dev/null +++ b/docs/design/ui_kits/app/Chart.js.txt @@ -0,0 +1,70 @@ +// Burn-up chart with Monte Carlo forecast cone — geometry, not decoration. +function BurnUpCone({ width = 640, height = 220 }) { + const pad = { l: 34, r: 96, t: 16, b: 26 }; + const W = width - pad.l - pad.r; + const H = height - pad.t - pad.b; + const x = (f) => pad.l + f * W; + const y = (f) => pad.t + (1 - f) * H; + + // scope line (total work), actual completed, cone from today + const today = 0.58; + const actual = [[0, 0], [0.08, 0.05], [0.18, 0.13], [0.26, 0.16], [0.36, 0.27], [0.46, 0.38], [0.58, 0.47]]; + const coneHi = [[0.58, 0.47], [0.72, 0.66], [0.86, 0.88], [0.95, 1.0]]; + const coneLo = [[0.58, 0.47], [0.74, 0.58], [0.9, 0.74], [1.0, 0.86]]; + const mid = [[0.58, 0.47], [0.76, 0.63], [0.92, 0.83], [1.0, 0.93]]; + const pts = (arr) => arr.map(([a, b]) => `${x(a)},${y(b)}`).join(' '); + const cone = [...coneHi, ...[...coneLo].reverse()]; + + return ( + + {/* gridlines */} + {[0, 0.25, 0.5, 0.75, 1].map((f) => ( + + ))} + {/* scope */} + + scope · 42 issues + {/* cone */} + + + + + {/* actual */} + + + {/* today rule */} + + today + {/* 80% band bracket */} + + 80% + Mar 3–12 + {/* x labels */} + Jan 6 + Mar 15 + + ); +} + +// Runway bar: milestone due date vs forecast range position +function RunwayBar({ m }) { + const toneColor = m.tone === 'warn' ? 'var(--warn)' : 'var(--ok)'; + const left = Math.max(0, (m.pos - m.spread / 2) * 100); + const w = Math.min(100 - left, m.spread * 100); + return ( +
+
+
+ {/* due marker */} +
+
+ ); +} + +Object.assign(window, { BurnUpCone, RunwayBar }); diff --git a/docs/design/ui_kits/app/ChatPanel.js.txt b/docs/design/ui_kits/app/ChatPanel.js.txt new file mode 100644 index 0000000..028ca6a --- /dev/null +++ b/docs/design/ui_kits/app/ChatPanel.js.txt @@ -0,0 +1,98 @@ +// Agent panel — chat is the write-path +function ChatPanel({ onOpenDirectives, offline }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Icon, IconButton } = DS; + const d = window.CT_DATA; + const [msgs, setMsgs] = React.useState(d.chat); + const [text, setText] = React.useState(''); + const [thinking, setThinking] = React.useState(false); + const scrollRef = React.useRef(null); + + React.useEffect(() => { + const el = scrollRef.current; + if (el) el.scrollTop = el.scrollHeight; + }, [msgs, thinking]); + + const send = () => { + const t = text.trim(); + if (!t) return; + setMsgs((m) => [...m, { from: 'user', text: t }]); + setText(''); + setThinking(true); + setTimeout(() => { + setThinking(false); + setMsgs((m) => [...m, { from: 'agent', text: d.cannedReply }]); + }, 900); + }; + + return ( + + ); +} +Object.assign(window, { ChatPanel }); diff --git a/docs/design/ui_kits/app/DepsGraph.js.txt b/docs/design/ui_kits/app/DepsGraph.js.txt new file mode 100644 index 0000000..aa248e0 --- /dev/null +++ b/docs/design/ui_kits/app/DepsGraph.js.txt @@ -0,0 +1,128 @@ +// Dependency graph drill-in — layered DAG, critical path in spruce +function DepsGraph({ onOpenIssue }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Tag, Icon } = DS; + const g = window.CT_DATA.deps; + + const PAD = 14, COLW = 206, ROWH = 106, NW = 176, NH = 84; + const X = (c) => PAD + c * COLW; + const Y = (r) => PAD + r * ROWH; + const maxCol = g.milestone.col; + const maxRow = Math.max(...g.nodes.map((n) => n.row), g.milestone.row); + const W = PAD * 2 + maxCol * COLW + NW; + const H = PAD * 2 + maxRow * ROWH + NH; + const MSW = 158, MSH = 44; + + const pos = {}; + g.nodes.forEach((n) => { pos[n.id] = { x: X(n.col), y: Y(n.row), w: NW, h: NH }; }); + pos['ms'] = { x: X(g.milestone.col), y: Y(g.milestone.row) + (NH - MSH) / 2, w: MSW, h: MSH }; + + const edgePath = (e) => { + const a = pos[e.from], b = pos[e.to]; + const x1 = a.x + a.w, y1 = a.y + a.h / 2; + const x2 = b.x, y2 = b.y + b.h / 2; + const dx = Math.max(28, (x2 - x1) / 2); + return `M ${x1} ${y1} C ${x1 + dx} ${y1}, ${x2 - dx} ${y2}, ${x2 - 5} ${y2}`; + }; + + const STATES = { + done: { icon: 'circle-check', color: 'var(--ok)', label: 'done' }, + steeping: { icon: 'clock', color: 'var(--warn)', label: 'steeping' }, + review: { icon: 'git-pull-request', color: 'var(--info)', label: 'in review' }, + triage: { icon: 'circle-dot', color: 'var(--ink-3)', label: 'triage' }, + diagnosis: { icon: 'circle-dashed', color: 'var(--ink-3)', label: 'diagnosis' }, + }; + + const Node = ({ n }) => { + const st = STATES[n.state]; + const crit = g.critical.includes(n.id); + return ( +
onOpenIssue({ id: n.id, title: n.title, labels: n.tags, rationale: n.rationale, days: n.state === 'steeping' ? n.days : undefined })} + style={{ + position: 'absolute', left: pos[n.id].x, top: pos[n.id].y, width: NW, height: NH, + background: n.state === 'done' ? 'var(--paper-2)' : 'var(--surface-card)', + border: `1px solid ${crit ? 'var(--spruce-5)' : 'var(--line-1)'}`, + boxShadow: crit ? 'var(--shadow-1), inset 2px 0 0 var(--accent)' : 'var(--shadow-1)', + borderRadius: 'var(--radius-2)', padding: '9px 11px', cursor: 'pointer', + display: 'flex', flexDirection: 'column', gap: 6, + opacity: n.state === 'done' ? 0.72 : 1, + transition: 'border-color var(--duration-fast) var(--ease-out)', + }} + onMouseEnter={(e) => { e.currentTarget.style.borderColor = crit ? 'var(--accent)' : 'var(--line-2)'; }} + onMouseLeave={(e) => { e.currentTarget.style.borderColor = crit ? 'var(--spruce-5)' : 'var(--line-1)'; }} + > +
{n.title}
+
+ #{n.id} + + + {st.label}{n.state === 'steeping' && n.days ? ` ${n.days}` : ''} + + {n.tags.filter((t) => t.startsWith('p/')).map((t) => ( + {t} + ))} +
+
+ ); + }; + + return ( +
+ {/* legend */} +
+ + critical path + + + blocks + + unattached: {g.unattached.map((i) => `#${i}`).join(' · ')} +
+ + {/* graph canvas */} +
+
+ + + + + + + + + + {g.edges.map((e, i) => ( + + ))} + + {g.nodes.map((n) => )} + {/* milestone terminal */} +
+ +
+ {g.milestone.name} + due {g.milestone.due} +
+
+
+
+ +

+ Four issues sit on the critical path, and #87 is the cork in the bottle. Remove it and everything pours. +

+
+ ); +} +Object.assign(window, { DepsGraph }); diff --git a/docs/design/ui_kits/app/DirectivesScreen.js.txt b/docs/design/ui_kits/app/DirectivesScreen.js.txt new file mode 100644 index 0000000..8698e5b --- /dev/null +++ b/docs/design/ui_kits/app/DirectivesScreen.js.txt @@ -0,0 +1,103 @@ +// Directive log — append-only ledger + the consequence diff (propose-approve) +function DirectivesScreen() { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Card, Button, Badge, Icon } = DS; + const d = window.CT_DATA.directives; + const [pending, setPending] = React.useState(d.pending); + const [entries, setEntries] = React.useState(d.entries); + + const resolve = (status) => { + setEntries((e) => [{ + seq: pending.seq, who: pending.who, when: pending.when, what: pending.what, why: 'pilot demo on the 14th', + status, consequence: status === 'applied' ? '#78 +5d · Beta 80% Mar 5–14' : 'withdrawn before apply', + }, ...e]); + setPending(null); + }; + + const toneColor = { ok: 'var(--ok)', warn: 'var(--warn)', info: 'var(--info)', danger: 'var(--danger)' }; + const statusBadge = { + applied: { tone: 'ok', label: 'applied' }, + withdrawn: { tone: 'neutral', label: 'withdrawn' }, + superseded: { tone: 'info', label: 'superseded' }, + }; + + return ( +
+
+

Directives

+

append-only · JSONL in pm-state · who, when, what, why

+
+ + {pending ? ( + + + + + }> +
+

+ {pending.who} + · {pending.when} +
“{pending.what}” +

+
+ {pending.diff.map((r) => ( +
+ + {r.change} + + {r.from} {r.to} + +
+ ))} +
+

+ Cheap, as consequences go. Shall I make it so? +

+
+
+ ) : ( +
+ + Nothing awaits your word. Directives are given in chat; consequences appear here first. +
+ )} + + +
+ {entries.map((e, i) => { + const sb = statusBadge[e.status]; + return ( +
+
+ #00{e.seq} + +
+
+
+ {e.who.split(' ').map((w) => w[0]).join('')} + {e.who} + {e.when} + {e.why ? · why: {e.why} : null} + {sb.label} +
+

“{e.what}”

+

{e.consequence}

+
+
+ ); + })} +
+
+ +

+ Entries are never edited. Corrections are new entries — the ledger remembers everything, politely. +

+
+ ); +} +Object.assign(window, { DirectivesScreen }); diff --git a/docs/design/ui_kits/app/FocusScreen.js.txt b/docs/design/ui_kits/app/FocusScreen.js.txt new file mode 100644 index 0000000..79978c0 --- /dev/null +++ b/docs/design/ui_kits/app/FocusScreen.js.txt @@ -0,0 +1,52 @@ +// Morning service — focus screen +function FocusScreen({ onOpenIssue }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Card, Tag, Badge, Button, IconButton } = DS; + const d = window.CT_DATA; + + const FocusRow = ({ slot, issue, jade }) => ( + { e.preventDefault(); onOpenIssue(issue); }} + style={{ color: 'inherit', border: 'none' }}>{issue.title}} + actions={} + footer={jade ? <> + + + scheduler pick · critical path + : null}> +
+ #{issue.id} + {issue.labels.map((l) => )} + {issue.steeping ? steeping {issue.steeping} : null} +
+

{issue.rationale}

+
+ ); + + return ( +
+
+
+

Morning service

+

{d.today} · reconcile 3.2s

+
+ ahead of forecast +
+ +
+ + + +
+ + 80% this lands Mar 3–12} + actions={}> + +

+ The cone has narrowed since Friday. I'm quietly pleased. +

+
+
+ ); +} +Object.assign(window, { FocusScreen }); diff --git a/docs/design/ui_kits/app/GanttView.js.txt b/docs/design/ui_kits/app/GanttView.js.txt new file mode 100644 index 0000000..99a512f --- /dev/null +++ b/docs/design/ui_kits/app/GanttView.js.txt @@ -0,0 +1,103 @@ +// Gantt drill-in — scheduler-derived bars, critical chain, 80% forecast tails +function GanttView({ onOpenIssue }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Icon } = DS; + const g = window.CT_DATA.gantt; + + const LABELW = 232, DAYW = 21, ROWH = 36, HEADH = 30; + const chartW = g.days * DAYW; + const W = LABELW + chartW; + const H = HEADH + g.rows.length * ROWH; + const X = (d) => LABELW + d * DAYW; + + const BAR = { + done: { bg: 'var(--paper-3)', border: 'transparent', text: 'var(--ink-3)' }, + steeping: { bg: 'var(--accent)', border: 'transparent', text: 'var(--ink-inverse)' }, + review: { bg: 'var(--info-tint)', border: 'var(--info)', text: 'var(--info)' }, + scheduled: { bg: 'var(--spruce-2)', border: 'var(--spruce-3)', text: 'var(--accent-text)' }, + }; + + return ( +
+ {/* legend */} +
+ {[['steeping', 'in work'], ['review', 'in review'], ['scheduled', 'scheduled'], ['done', 'done']].map(([k, label]) => ( + + {label} + + ))} + + 80% tail + + + today + +
+ + {/* chart */} +
+
+ {/* week gridlines + labels */} + {g.weeks.map((w) => ( + +
+ {w.label} +
+ ))} + {/* milestone 80% band */} +
+ {g.band.label} + {/* due marker */} +
+ + {/* today rule */} +
+ + {/* rows */} + {g.rows.map((r, i) => { + const top = HEADH + i * ROWH; + const st = BAR[r.state]; + return ( + + {/* row hairline */} +
+ {/* label cell (sticky) */} +
onOpenIssue({ id: r.id, title: r.title, labels: [], days: r.state === 'steeping' ? '4d' : undefined })} + style={{ + position: 'absolute', left: 0, width: LABELW, height: ROWH, top: top, zIndex: 2, cursor: 'pointer', + display: 'flex', alignItems: 'center', gap: 8, padding: '0 12px 0 14px', background: 'var(--surface-card)', + borderRight: '1px solid var(--line-1)', + boxShadow: r.crit ? 'inset 2px 0 0 var(--accent)' : 'none', + }} + > + #{r.id} + {r.title} + {r.who} +
+ {/* bar */} +
+ {/* 80% tail */} + {r.p80 ? ( + <> +
+
+ + ) : null} +
+ ); + })} +
+
+ +

+ The path holds if #87 lands by Wednesday. The dotted tails are your own history, wagging. +

+
+ ); +} +Object.assign(window, { GanttView }); diff --git a/docs/design/ui_kits/app/InboxScreen.js.txt b/docs/design/ui_kits/app/InboxScreen.js.txt new file mode 100644 index 0000000..3ba2487 --- /dev/null +++ b/docs/design/ui_kits/app/InboxScreen.js.txt @@ -0,0 +1,95 @@ +// Inbox — Reginald only rings the bell when it matters +function InboxScreen({ onOpenIssue, onOpenDirectives, readIds, setReadIds }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Card, Tabs, Button, Icon } = DS; + const all = window.CT_DATA.inbox; + const [tab, setTab] = React.useState('all'); + + const isRead = (n) => !n.unread || readIds.includes(n.id); + const unreadCount = all.filter((n) => !isRead(n)).length; + + const FILTERS = { + all: () => true, + mentions: (n) => n.type === 'mention' || n.type === 'assignment', + drift: (n) => n.type === 'drift' || n.type === 'nag' || n.type === 'milestone', + system: (n) => n.type === 'system' || n.type === 'review', + }; + const items = all.filter(FILTERS[tab]); + const days = [...new Set(items.map((n) => n.day))]; + + const toneColor = { ok: 'var(--ok)', warn: 'var(--warn)', info: 'var(--info)', neutral: 'var(--ink-3)' }; + + const open = (n) => { + setReadIds((r) => (r.includes(n.id) ? r : [...r, n.id])); + if (n.issue) onOpenIssue(n.issue); + else if (n.to === 'directives') onOpenDirectives(); + }; + + return ( +
+
+
+

Inbox

+

+ {unreadCount ? `${unreadCount} unread` : 'all read'} · nothing here rings twice +

+
+ {unreadCount ? ( + + ) : null} +
+ + + + +
+ {days.map((day) => ( +
+
{day}
+ {items.filter((n) => n.day === day).map((n) => { + const read = isRead(n); + return ( +
open(n)} + style={{ + display: 'flex', alignItems: 'flex-start', gap: 12, padding: '11px 20px', + cursor: n.issue || n.to ? 'pointer' : 'default', + opacity: read ? 0.72 : 1, + }} + onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--paper-2)'; }} + onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }} + > + + + + +
+
+ {n.who ? {n.who} : null}{n.text} +
+
{n.detail}
+
+ {n.time} +
+ ); + })} +
+ ))} +
+
+ +

+ I only ring the bell when it matters. The rest can wait for morning service. +

+
+ ); +} +Object.assign(window, { InboxScreen }); diff --git a/docs/design/ui_kits/app/IssueScreen.js.txt b/docs/design/ui_kits/app/IssueScreen.js.txt new file mode 100644 index 0000000..0433ef1 --- /dev/null +++ b/docs/design/ui_kits/app/IssueScreen.js.txt @@ -0,0 +1,172 @@ +// Issue detail — human intent (gitea) on the left, machine-derived (pm-state) on the right +function IssueScreen({ issue, onBack, onOpenIssue }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Card, Tag, Badge, Button, Icon } = DS; + const det = window.CT_DATA.issueDetail[issue.id] || { + state: 'triage', + assignee: 'Stephen', + milestone: 'Beta', + body: '', + comments: [], + lifecycle: [ + { stage: 'Diagnosis', event: 'issue opened', when: 'Feb 4 · 10:20', icon: 'circle-dot', done: true }, + { stage: 'Triage', event: 'labeled · milestoned Beta', when: 'Feb 5 · 09:12', icon: 'tag', done: true }, + { stage: 'Work start', event: 'first branch or commit ref', when: 'pending', icon: 'git-commit-horizontal', done: false }, + { stage: 'Deploy', event: 'PR merged', when: 'pending', icon: 'git-merge', done: false }, + { stage: 'Complete', event: 'issue closed', when: 'pending', icon: 'circle-check', done: false }, + ], + forecast: { p80: 'starts wk of Feb 16', note: 'queue position from scheduler' }, + blocks: [], + blockedBy: [], + note: null, + }; + const stateBadge = { + steeping: { tone: 'warn', label: `steeping${issue.days || issue.steeping ? ' ' + (issue.days || issue.steeping) : ''}` }, + triage: { tone: 'neutral', label: 'triage' }, + review: { tone: 'info', label: 'in review' }, + done: { tone: 'ok', label: 'done' }, + }[det.state] || { tone: 'neutral', label: det.state }; + + return ( +
+ {/* breadcrumb + header */} +
+ +
+
+

#{issue.id} · stephen/commitea

+

{issue.title}

+
+ {stateBadge.label} + {(issue.labels || []).map((l) => )} + + {det.milestone} + + + {det.assignee} + +
+
+ +
+
+ +
+ {/* left: human intent */} +
+ + {det.body ? ( +

{det.body}

+ ) : ( +

+ No description was written. I have opinions about that, but I'll keep them warm. +

+ )} +
+ + +
+ {det.comments.map((c, i) => ( +
+ {c.who.split(' ').map((w) => w[0]).join('')} +
+
+ {c.who} + {c.when} +
+

{c.text}

+
+
+ ))} +
+ + +
+
+
+ + {det.note ? ( +

{det.note}

+ ) : null} +
+ + {/* right: machine-derived sidecar */} + +
+ {/* lifecycle */} +
+ {det.lifecycle.map((s, i) => ( +
+
+ + + + {i < det.lifecycle.length - 1 ? : null} +
+
+
{s.stage}
+
{s.event}
+
{s.when}
+
+
+ ))} +
+ {/* forecast */} +
+
Forecast
+
80% {det.forecast.p80}
+
{det.forecast.note}
+
+ {/* dependencies */} +
+
Dependencies
+ {det.blocks.length === 0 && det.blockedBy.length === 0 ? ( +
none
+ ) : ( +
+ {det.blocks.length ? ( +
+ blocks + {det.blocks.map((b) => ( + + ))} +
+ ) : null} + {det.blockedBy.length ? ( +
+ blocked by + {det.blockedBy.map((b) => #{b})} +
+ ) : null} +
+ )} +
+ {/* provenance note */} +
+

+ Lives in pm-state. Your repo never sees any of it. +

+
+
+
+
+
+ ); +} +Object.assign(window, { IssueScreen }); diff --git a/docs/design/ui_kits/app/MilestoneScreen.js.txt b/docs/design/ui_kits/app/MilestoneScreen.js.txt new file mode 100644 index 0000000..eecd1af --- /dev/null +++ b/docs/design/ui_kits/app/MilestoneScreen.js.txt @@ -0,0 +1,98 @@ +// Milestone detail — scope, cone, issues; forecasts stay ranges +function MilestoneScreen({ onBack, onOpenIssue }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Card, Tag, Badge, Button, Icon } = DS; + const cols = window.CT_DATA.columns; + const byState = (ids) => cols.flatMap((c) => c.issues.map((i) => ({ ...i, col: c.label }))).filter((i) => ids.includes(i.id)); + + const groups = [ + { label: 'Steeping', issues: byState([87, 84]) }, + { label: 'In review', issues: byState([92]) }, + { label: 'Queued', issues: byState([102, 103, 99, 96, 78]) }, + { label: 'Done', issues: byState([71, 69, 65]), muted: true }, + ]; + + const Stat = ({ label, value, tone }) => ( +
+
{label}
+
{value}
+
+ ); + + return ( +
+
+ +
+
+

+ milestone · due Mar 15 · soft — scope may flex +

+

Beta

+
+ ahead of forecast + 80% Mar 3–12 +
+
+ +
+
+ + {/* stats strip */} + +
+ + + +
+
Drift · 7d
+
−2d · cone narrowed
+
+
+
+ +
+ 80% this lands Mar 3–12} jade> + +

+ Comfortably ahead. Beta needs #87 more than it needs my commentary. +

+
+ + +
+ {groups.map((g) => ( +
+
+ {g.label} + {g.issues.length} +
+ {g.issues.map((i) => ( +
onOpenIssue({ id: i.id, title: i.title, labels: i.labels, days: i.days })} + style={{ + display: 'flex', alignItems: 'center', gap: 8, padding: '7px 20px', cursor: 'pointer', + opacity: g.muted ? 0.6 : 1, + }} + onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--paper-2)'; }} + onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }} + > + #{i.id} + {i.title} + {(i.labels || []).filter((l) => l.startsWith('est/')).map((l) => )} +
+ ))} +
+ ))} +
+
+
+
+ ); +} +Object.assign(window, { MilestoneScreen }); diff --git a/docs/design/ui_kits/app/OnboardingScreen.js.txt b/docs/design/ui_kits/app/OnboardingScreen.js.txt new file mode 100644 index 0000000..13c2db7 --- /dev/null +++ b/docs/design/ui_kits/app/OnboardingScreen.js.txt @@ -0,0 +1,167 @@ +// Onboarding / first connect — welcome → connect gitea → choose repo → bootstrap +function OnboardingScreen({ onDone }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Button, Input, Radio, Tag, Badge, Icon } = DS; + const [step, setStep] = React.useState(0); + const [conn, setConn] = React.useState('idle'); // idle | testing | ok + const [repo, setRepo] = React.useState('stephen/commitea'); + const [boot, setBoot] = React.useState(-1); // -1 idle, 0..2 running, 3 done + + React.useEffect(() => { + if (conn !== 'testing') return; + const t = setTimeout(() => setConn('ok'), 1100); + return () => clearTimeout(t); + }, [conn]); + + React.useEffect(() => { + if (boot < 0 || boot >= 3) return; + const t = setTimeout(() => setBoot(boot + 1), 700); + return () => clearTimeout(t); + }, [boot]); + + const STEPS = ['Welcome', 'Connect', 'Repo', 'Bootstrap']; + const BOOT_TASKS = [ + 'Create stephen/pm-state (the sidecar)', + 'Apply the label schema to stephen/commitea', + 'Install a webhook · endpoint :48731', + ]; + + const Frame = ({ children, footer }) => ( +
+ {children} + {footer ?
{footer}
: null} +
+ ); + + return ( +
+ {/* brand */} +
+ + + CommiTea + +
+ + {/* stepper */} +
+ {STEPS.map((s, i) => ( +
+ + {i < step ? '\u2713' : i + 1} + {s} + + {i < STEPS.length - 1 ? : null} +
+ ))} +
+ +
+ {step === 0 ? ( + setStep(1)}>Begin}> +

Good morning.

+

+ I'm Reginald, your project manager. I interview you instead of making you fill in forms, + I forecast in honest ranges, and I never do the arithmetic myself — there's a scheduler for that. +

+

+ Your plans live in your own Gitea as ordinary issues and labels. Delete me and nothing human is lost. +

+ + ) : null} + + {step === 1 ? ( + + + + }> +

Your Gitea

+ + +
+ + {conn === 'ok' ? connected · 3 repos visible : null} +
+ + ) : null} + + {step === 2 ? ( + + + + }> +

Which repo shall I manage?

+
+ {['stephen/commitea', 'stephen/novelpad', 'stephen/infra'].map((r) => ( + + ))} +
+

One to start. You can add more later in Settings.

+ + ) : null} + + {step === 3 ? ( + + + + : <> + + + }> +

+ {boot === 3 ? 'All set.' : 'With your approval'} +

+
+ {BOOT_TASKS.map((t, i) => ( +
+ i ? 'var(--ok)' : boot === i ? 'var(--warn)' : 'var(--ink-3)' }}> + i ? 'circle-check' : boot === i ? 'loader-circle' : 'circle-dashed'} size={15} /> + + i ? 'var(--ink-1)' : 'var(--ink-2)', whiteSpace: 'nowrap' }}>{t} +
+ ))} +
+ {['est/1d', 'est/2d', 'est/3d', 'est/5d', 'est/8d', 'p/1', 'p/2', 'p/3', 'p/4', 'deadline/hard'].map((l) => )} +
+
+ {boot === 3 ? ( +

+ The pot is empty. Tell me what you're planning and I'll draw up the tickets. +

+ ) : ( +

+ No bot comments, no body frontmatter, no synthetic issues — ever. Labels are the only footprint. +

+ )} + + ) : null} +
+ + first run · everything reversible +
+ ); +} +Object.assign(window, { OnboardingScreen }); diff --git a/docs/design/ui_kits/app/README.md b/docs/design/ui_kits/app/README.md new file mode 100644 index 0000000..d48df7a --- /dev/null +++ b/docs/design/ui_kits/app/README.md @@ -0,0 +1,22 @@ +# CommiTea app — UI kit + +High-fidelity recreation of the CommiTea Electron app (one window). Built from the product plan (`uploads/commitea.md`); no production UI existed yet, so this kit **is** the reference design. + +Surfaces (per the plan's UI section): + +- **Onboarding / first run** ("First run" in the nav) — full-window takeover, no rail or chat: Reginald introduces himself → connect Gitea (test connection) → choose the managed repo → propose-approve bootstrap (pm-state sidecar, label schema, webhook) ending at "Start a capture". +- **Standup** — the morning ritual as a typeset letter from Reginald: overnight drift report (with mono consequence deltas), today's plan per person, and the stale-blocker nag. Sections settle in once, 320ms, reduced-motion-safe. +- **Morning service (Focus)** — Now/Next/Later focus cards with the scheduler's pick and Reginald's one-line rationale; burn-up chart with Monte Carlo forecast cone. +- **Board** — kanban over the inferred lifecycle (diagnosis → triage → steeping → review → done), with Gantt and Dependencies drill-in tabs: the Gantt shows scheduler-derived bars with 80% forecast tails and Beta's landing band; Dependencies is a layered DAG with the critical path in spruce. +- **Issue detail** (click any issue anywhere) — the purity rule made visible: human intent from gitea on the left (description, comments, composer that "writes to gitea, as you"), machine-derived sidecar on the right (inferred lifecycle timeline from git events, per-issue forecast, dependencies) with the provenance note "Lives in pm-state. Your repo never sees any of it." +- **Inbox** — the bell Reginald only rings when it matters: drift, mentions, nags, reviews, system events — day-grouped, filter tabs, unread dots with a live count in the rail; rows navigate to the issue or directive concerned. "Nothing here rings twice." +- **Directives** — the append-only ledger (who/when/what/why) plus the consequence diff: scheduler re-runs after a directive and Reginald presents before → after deltas for approval ("Make it so" / Amend / Withdraw). Also reachable from the history icon in Reginald's panel. +- **Runway** — capacity vs milestone dates; forecast ranges, never point dates. Its calibration card drills into the **Calibration report**: estimate-vs-actual scatter ("the shape of hope") with honest-diagonal and fitted ×1.18 line, bias by estimate label, per-person bias, and the effect on forecast bands. Milestone rows drill into **Milestone detail**: stats strip (scope / done / forecast / drift), burn-up cone, and the issue list grouped by lifecycle state. +- **Capture** — the plan's headline goal: braindump → interview → approved ticket set in under 2:00, with a running clock. Reginald asks only what he can't infer; drafts build in "the tray" as you answer; review shows the consequence (cone shift) before "Approve all"; filing touches labels only. +- **Reginald's panel** (right side, always present) — chat is the write-path; UI is the read-path. Reginald speaks in upright Caslon serif. + +- **States** (nav, bottom) — gallery of empty & trouble states as wired in the app: empty pot / nothing scheduled / no directives / search miss; gitea-unreachable banner, model-offline chat state, webhook poll-fallback, failed first reconcile. Two are live: board search shows the real empty state, and clicking the rail's connection dot toggles offline mode (banner app-wide, Reginald queues writes, composer disabled). + +Interactive: nav switching, issue click → detail page, chat replies (canned), light/dark theme toggle, offline simulation. + +Files: `index.html` (entry), `Shell.jsx` (rail + layout), `OnboardingScreen.jsx`, `StandupScreen.jsx`, `FocusScreen.jsx`, `InboxScreen.jsx`, `BoardScreen.jsx`, `DepsGraph.jsx`, `GanttView.jsx`, `RunwayScreen.jsx`, `CalibrationScreen.jsx`, `MilestoneScreen.jsx`, `CaptureScreen.jsx`, `DirectivesScreen.jsx`, `IssueScreen.jsx`, `SettingsScreen.jsx`, `StatesGallery.jsx`, `ChatPanel.jsx`, `Chart.jsx` (burn-up cone SVG), `data.js` (fixture data). diff --git a/docs/design/ui_kits/app/RunwayScreen.js.txt b/docs/design/ui_kits/app/RunwayScreen.js.txt new file mode 100644 index 0000000..5060ebf --- /dev/null +++ b/docs/design/ui_kits/app/RunwayScreen.js.txt @@ -0,0 +1,73 @@ +// Runway — capacity vs milestone dates; ranges, never points +function RunwayScreen({ onOpenCalibration, onOpenMilestone }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Card, Badge, Tag, Icon } = DS; + const d = window.CT_DATA; + + return ( +
+
+

Runway

+

capacity vs milestone dates · calibrated on 27 closed issues

+
+ + +
+ {d.runway.map((m, i) => ( +
{ e.currentTarget.style.background = 'var(--paper-2)'; }} + onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}> +
+
+ {m.name} +
+
+ due {m.due}{m.hard ? ' ' : ''} +
+ {m.hard ? : null} +
+ + 80% {m.p80} + {m.note} +
+ ))} +
+
+ +
+ +
+ {d.capacity.map((p, i) => ( +
+ {p.who.split(' ').map(w => w[0]).join('')} +
+
{p.who}
+
{p.slices}
+
+ {p.hours} +
+ ))} +
+
+ }> +

+ Your estimates run 18% optimistic on est/3d and above. Smaller tickets are honest. +

+

+ I widen the cone accordingly. No judgement — it's the most common shape of hope. +

+
+
+
+ ); +} +Object.assign(window, { RunwayScreen }); diff --git a/docs/design/ui_kits/app/SettingsScreen.js.txt b/docs/design/ui_kits/app/SettingsScreen.js.txt new file mode 100644 index 0000000..565bbb7 --- /dev/null +++ b/docs/design/ui_kits/app/SettingsScreen.js.txt @@ -0,0 +1,136 @@ +// Settings — gitea connection, sync, model roles, labels, rituals, appearance +function SettingsScreen({ dark, setDark }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Card, Input, Select, Switch, Radio, Button, IconButton, Tag, Badge, Icon } = DS; + const [webhooks, setWebhooks] = React.useState(true); + const [reconcile, setReconcile] = React.useState(true); + const [poll, setPoll] = React.useState(true); + const [nag, setNag] = React.useState(true); + + const Row = ({ children, style }) => ( +
{children}
+ ); + const Note = ({ children }) => ( +

{children}

+ ); + + return ( +
+
+

Settings

+

config lives in pm-state · versioned, portable

+
+ + +
+ + +
+ Managed repos + + + stephen/commitea + syncing + + + + + stephen/pm-state + sidecar + + The sidecar holds machine-derived state only. Delete it and resync — no truth is lost. + +
+
+
+ + +
+ + setWebhooks(e.target.checked)} /> + endpoint :48731 · healthy + + setReconcile(e.target.checked)} /> + + setPoll(e.target.checked)} /> +
+ + + +
+ + hot memory ≤ 2k tokens · math is never delegated to either + +

+ The small one writes my standup; the large one argues with your estimates. Neither is allowed near the arithmetic. +

+
+
+ + +
+ + {['est/1d', 'est/2d', 'est/3d', 'est/5d', 'est/8d'].map((l) => )} + + + {['p/1', 'p/2', 'p/3', 'p/4'].map((l) => )} + + + Fixed sets, human-meaningful, visible in gitea. Not configurable — that is rather the point. +
+
+ + +
+ + Morning standup +
+ +
+
+
+
+ + +
+ setDark(false)} /> + setDark(true)} /> +
+
+ + + +
+
Forget this gitea
+ Removes the connection and the local cache. Gitea itself is untouched. +
+ +
+
+
+ ); +} +Object.assign(window, { SettingsScreen }); diff --git a/docs/design/ui_kits/app/Shell.js.txt b/docs/design/ui_kits/app/Shell.js.txt new file mode 100644 index 0000000..4c8ac38 --- /dev/null +++ b/docs/design/ui_kits/app/Shell.js.txt @@ -0,0 +1,113 @@ +// App shell — left rail + content + agent panel, issue page, theme toggle +function Shell() { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Icon, Switch } = DS; + const [view, setView] = React.useState('focus'); + const [prevView, setPrevView] = React.useState('focus'); + const [dark, setDark] = React.useState(false); + const [issue, setIssue] = React.useState(null); + const [offline, setOffline] = React.useState(false); + const [readIds, setReadIds] = React.useState([]); + const inboxUnread = window.CT_DATA.inbox.filter((n) => n.unread && !readIds.includes(n.id)).length; + + const openIssue = (i) => { + setIssue(i); + if (view !== 'issue') setPrevView(view); + setView('issue'); + }; + + React.useEffect(() => { + document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light'); + }, [dark]); + + if (view === 'firstrun') { + return setView(dest)} />; + } + + const NAV = [ + { id: 'standup', label: 'Standup', icon: 'sun' }, + { id: 'focus', label: 'Morning service', icon: 'coffee' }, + { id: 'inbox', label: 'Inbox', icon: 'bell', count: inboxUnread || null }, + { id: 'capture', label: 'Capture', icon: 'plus' }, + { id: 'board', label: 'The pot', icon: 'square-kanban' }, + { id: 'runway', label: 'Runway', icon: 'chart-line' }, + { id: 'directives', label: 'Directives', icon: 'flag' }, + ]; + + const NavItem = ({ item }) => { + const active = view === item.id || (view === 'issue' && prevView === item.id) || ((view === 'calibration' || view === 'milestone') && item.id === 'runway'); + return ( + + ); + }; + + return ( +
+ {/* left rail */} + + + {/* main */} +
+
+ {offline ? : null} + {view === 'focus' ? : null} + {view === 'board' ? : null} + {view === 'runway' ? setView('calibration')} onOpenMilestone={() => setView('milestone')} /> : null} + {view === 'capture' ? setView('focus')} /> : null} + {view === 'standup' ? setView('focus')} onOpenIssue={openIssue} /> : null} + {view === 'settings' ? : null} + {view === 'directives' ? : null} + {view === 'issue' && issue ? setView(prevView)} onOpenIssue={openIssue} /> : null} + {view === 'calibration' ? setView('runway')} /> : null} + {view === 'milestone' ? setView('runway')} onOpenIssue={openIssue} /> : null} + {view === 'states' ? setView('capture')} /> : null} + {view === 'inbox' ? setView('directives')} readIds={readIds} setReadIds={setReadIds} /> : null} +
+
+ + setView('directives')} offline={offline} /> +
+ ); +} +Object.assign(window, { Shell }); diff --git a/docs/design/ui_kits/app/StandupScreen.js.txt b/docs/design/ui_kits/app/StandupScreen.js.txt new file mode 100644 index 0000000..da2641c --- /dev/null +++ b/docs/design/ui_kits/app/StandupScreen.js.txt @@ -0,0 +1,112 @@ +// Morning standup ritual — a typeset letter from Reginald: drift, plan, nag +const standupCSS = ` +@keyframes ct-standup-settle { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: none; } +} +.ct-standup-section { opacity: 1; } +@media (prefers-reduced-motion: no-preference) { + .ct-standup-section { animation: ct-standup-settle 320ms var(--ease-out) both; } +} +`; +(function inject() { + if (typeof document !== 'undefined' && !document.getElementById('ct-standup-css')) { + const s = document.createElement('style'); s.id = 'ct-standup-css'; s.textContent = standupCSS; + document.head.appendChild(s); + } +})(); + +function StandupScreen({ onBegin, onOpenIssue }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Button, Tag, Badge, Icon } = DS; + const s = window.CT_DATA.standup; + + const Section = ({ overline, children, order }) => ( +
+

{overline}

+ {children} +
+ ); + + const toneColor = { ok: 'var(--ok)', warn: 'var(--warn)', danger: 'var(--danger)' }; + + return ( +
+
+ {/* letterhead */} +
+

{s.date} · prepared 07:00

+

Morning standup

+
+ +
+
+ {s.drift.map((d) => ( +
+ + {d.text} + {d.delta} +
+ ))} +
+
+ +
+
+ {s.plan.map((p) => ( +
+ {p.who.split(' ').map((w) => w[0]).join('')} +
+
+ {p.who} + {p.pick} + {p.title} +
+

{p.why}

+
+
+ ))} +
+
+ +
+
onOpenIssue({ id: s.nag.id, title: 'Fix lifecycle inference on merge events', labels: ['est/2d', 'p/1'], days: s.nag.days })} + style={{ + display: 'flex', gap: 10, alignItems: 'flex-start', cursor: 'pointer', + background: 'var(--warn-tint)', borderRadius: 'var(--radius-2)', padding: '12px 14px', + }} + > + +
+
+ #{s.nag.id} + steeping {s.nag.days} + blocks {s.nag.blocks.join(', ')} +
+

{s.nag.text}

+
+
+
+ + {/* sign-off */} +
+

The kettle's on. — R.

+ + +
+
+
+ ); +} +Object.assign(window, { StandupScreen }); diff --git a/docs/design/ui_kits/app/StatesGallery.js.txt b/docs/design/ui_kits/app/StatesGallery.js.txt new file mode 100644 index 0000000..5c6a129 --- /dev/null +++ b/docs/design/ui_kits/app/StatesGallery.js.txt @@ -0,0 +1,130 @@ +// Shared empty/trouble states + the States gallery screen +function EmptyState({ icon, title, line, action, onAction, compact }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Button, Icon } = DS; + return ( +
+ +
{title}
+

{line}

+ {action ? : null} +
+ ); +} + +function OfflineBanner({ retryIn }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Icon } = DS; + return ( +
+ + + Gitea isn't answering. I'll keep trying and say nothing more about it. + + + retry in {retryIn || '0:12'} · reads from cache + +
+ ); +} + +function ModelAwayState() { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Icon, Badge } = DS; + return ( +
+
+ + Reginald + model offline + queued: 1 directive +
+

+ The model is away from its desk. Reads still work; writes will wait their turn. +

+
+ ); +} + +function StatesScreen({ onCapture }) { + const DS = window.CommiTeaDesignSystem_20e63b; + const { Button, Badge, Icon } = DS; + + const Specimen = ({ label, children }) => ( +
+ {label} +
+ {children} +
+
+ ); + + return ( +
+
+

States

+

empty & trouble · specimens as wired in the app

+
+ +

Empty

+
+ + + + + + + + + + + + +
+ +

Trouble

+
+ +
+
+
+ + + + +
+ webhooks down + polling every 2 min · updates may lag +
+
+
+ +
+ +
+ + +
+
+
+
+
+ ); +} +Object.assign(window, { EmptyState, OfflineBanner, ModelAwayState, StatesScreen }); diff --git a/docs/design/ui_kits/app/data.js b/docs/design/ui_kits/app/data.js new file mode 100644 index 0000000..1116331 --- /dev/null +++ b/docs/design/ui_kits/app/data.js @@ -0,0 +1,203 @@ +// CommiTea fixture data — human-authored intent (gitea) + machine-derived state (sidecar) +window.CT_DATA = { + today: 'Tuesday 7 July 2026', + milestone: { + name: 'Beta', + due: '2026-03-15', + hard: false, + forecast: { p80: 'Mar 3–12', p50: 'Mar 5–8', drift: '+0d' }, + }, + focus: { + now: { + id: 87, title: 'Fix lifecycle inference on merge events', + labels: ['est/2d', 'p/1'], steeping: '4d', + rationale: 'It blocks #91 and #92. I\u2019d take it first \u2014 the critical path agrees with me.', + }, + next: { + id: 91, title: 'Webhook listener: reconcile on reconnect', + labels: ['est/3d', 'p/2'], + rationale: 'Ready the moment #87 lands. The estimate is yours; history says add a day.', + }, + later: { + id: 78, title: 'Calibration store cold-start distributions', + labels: ['est/5d', 'p/3'], + rationale: 'Nothing depends on it yet. It can steep.', + }, + }, + columns: [ + { id: 'diagnosis', label: 'Diagnosis', issues: [ + { id: 102, title: 'Scheduler ignores standing allocation slices', labels: ['p/2'], who: 'SM' }, + { id: 103, title: 'Dark theme: cone fill too faint', labels: ['p/4'], who: 'SM' }, + ]}, + { id: 'triage', label: 'Triage', issues: [ + { id: 99, title: 'Directive log: conflict-free merge test', labels: ['est/1d', 'p/2'], who: 'SM' }, + { id: 96, title: 'Label schema bootstrap for new repos', labels: ['est/2d', 'p/3'], who: 'AK' }, + { id: 78, title: 'Calibration store cold-start distributions', labels: ['est/5d', 'p/3'], who: 'SM' }, + ]}, + { id: 'steeping', label: 'Steeping', issues: [ + { id: 87, title: 'Fix lifecycle inference on merge events', labels: ['est/2d', 'p/1'], who: 'SM', blocked: false, days: '4d' }, + { id: 84, title: 'Capacity model: focus factor per person', labels: ['est/3d', 'p/2'], who: 'AK', days: '1d' }, + ]}, + { id: 'review', label: 'In review', issues: [ + { id: 92, title: 'Monte Carlo engine: percentile bands', labels: ['est/5d', 'p/1'], who: 'SM', pr: '#141' }, + ]}, + { id: 'done', label: 'Done', issues: [ + { id: 71, title: 'Gitea client: token auth + retries', labels: ['est/2d', 'p/2'], who: 'SM' }, + { id: 69, title: 'pm-state repo bootstrap', labels: ['est/1d', 'p/1'], who: 'SM' }, + { id: 65, title: 'Electron shell + window state', labels: ['est/3d', 'p/2'], who: 'AK' }, + ]}, + ], + runway: [ + { name: 'Beta', due: 'Mar 15', hard: false, p80: 'Mar 3–12', pos: 0.62, spread: 0.18, tone: 'ok', note: 'ahead' }, + { name: 'Pilot-ready', due: 'Apr 30', hard: true, p80: 'Apr 21 – May 9', pos: 0.94, spread: 0.26, tone: 'warn', note: 'drifting' }, + { name: 'v1.0', due: 'Jun 12', hard: false, p80: 'May 30 – Jun 20', pos: 0.88, spread: 0.3, tone: 'ok', note: 'on watch' }, + ], + capacity: [ + { who: 'Stephen', hours: '5.2h/day', focus: 0.65, slices: 'dev 70% · pilots 20% · compliance 10%' }, + { who: 'Ana K.', hours: '3.8h/day', focus: 0.8, slices: 'dev 100%' }, + ], + chat: [ + { from: 'agent', text: 'Morning service. Two things drifted overnight; one needs your opinion.' }, + { from: 'agent', text: '#84 grew a dependency on #92 \u2014 I\u2019ve reordered. And #87 has been steeping for four days; it blocks two others. Worth a look.' }, + { from: 'user', text: 'Push the calibration work to next week, pilots come first' }, + { from: 'agent', text: 'Done \u2014 #78 moves to next week. Milestone Beta is unmoved; the cone doesn\u2019t care for calibration either. Logged as a directive.' }, + ], + cannedReply: 'Noted and logged as a directive. The scheduler is re-running \u2014 I\u2019ll show you the consequence diff in a moment.', + standup: { + date: 'Tuesday 7 July 2026', + drift: [ + { tone: 'warn', text: '#84 grew a dependency on #92 overnight.', delta: 'reordered \u00b7 no date impact' }, + { tone: 'warn', text: '#92 has sat in review for two days.', delta: 'Beta 80% +1d if idle past Thu' }, + { tone: 'ok', text: 'Webhook outage 02:14\u201302:31; full reconcile ran.', delta: 'nothing lost' }, + ], + plan: [ + { who: 'Stephen', pick: '#87', title: 'Fix lifecycle inference on merge events', why: 'It blocks two others and the critical path runs straight through it.' }, + { who: 'Ana K.', pick: '#84', title: 'Capacity model: focus factor per person', why: 'Already steeping \u2014 finish it before anything new is poured.' }, + ], + nag: { id: 87, days: '4d', blocks: ['#91', '#92'], text: 'Four days is a long steep. It blocks #91 and #92 \u2014 worth a look before it stains.' }, + }, + calibration: { + n: 27, + active: true, + labels: [ + { label: 'est/1d', n: 8, median: '1.1d', bias: 8 }, + { label: 'est/2d', n: 9, median: '2.4d', bias: 18 }, + { label: 'est/3d', n: 6, median: '3.7d', bias: 22 }, + { label: 'est/5d', n: 3, median: '6.5d', bias: 30 }, + { label: 'est/8d', n: 1, median: '8.5d', bias: null }, + ], + people: [ + { who: 'Stephen', n: 19, bias: 21, note: 'optimism grows with ticket size' }, + { who: 'Ana K.', n: 8, bias: 9, note: 'close to honest \u2014 suspicious' }, + ], + scatter: [ + [1, 1], [1, 1.2], [1, 0.9], [1, 1.5], [1, 1.1], [1, 0.8], [1, 1.3], [1, 1.2], + [2, 2], [2, 2.5], [2, 3.1], [2, 2.2], [2, 2.6], [2, 1.8], [2, 2.4], [2, 2.9], [2, 2.3], + [3, 3.5], [3, 4.1], [3, 3.1], [3, 4.6], [3, 3.6], [3, 3.9], + [5, 6.1], [5, 7.2], [5, 6.4], + [8, 8.5], + ], + fit: 1.18, + effect: { raw: '42d of estimates', banded: '80% band 46\u201353d', p50: '48d' }, + }, + inbox: [ + { id: 1, day: 'Today', type: 'drift', icon: 'chart-line', tone: 'warn', text: 'Beta\u2019s 80% window moved', detail: 'Mar 3\u201312 \u2192 Mar 5\u201314 \u00b7 directive #007', time: '09:14', unread: true, to: 'directives' }, + { id: 2, day: 'Today', type: 'mention', icon: 'message-square', tone: 'info', who: 'Ana K.', text: 'mentioned you on #92', detail: '\u201cpercentile bands ready for a second pair of eyes\u201d', time: '08:52', unread: true, issue: { id: 92, title: 'Monte Carlo engine: percentile bands', labels: ['est/5d', 'p/1'] } }, + { id: 3, day: 'Today', type: 'nag', icon: 'clock', tone: 'warn', text: '#87 is steeping \u00b7 4d', detail: 'blocks #91 and #92 \u2014 worth a look before it stains', time: '07:00', unread: true, issue: { id: 87, title: 'Fix lifecycle inference on merge events', labels: ['est/2d', 'p/1'], days: '4d' } }, + { id: 4, day: 'Yesterday', type: 'review', icon: 'git-pull-request', tone: 'info', text: 'PR #141 awaits review', detail: '#92 \u00b7 idle two days', time: '16:20', unread: false, issue: { id: 92, title: 'Monte Carlo engine: percentile bands', labels: ['est/5d', 'p/1'] } }, + { id: 5, day: 'Yesterday', type: 'assignment', icon: 'user', tone: 'neutral', who: 'Ana K.', text: 'took #96 from the pot', detail: 'Label schema bootstrap for new repos', time: '11:03', unread: false, issue: { id: 96, title: 'Label schema bootstrap for new repos', labels: ['est/2d', 'p/3'] } }, + { id: 6, day: 'Yesterday', type: 'system', icon: 'refresh-cw', tone: 'ok', text: 'Webhook outage 02:14\u201302:31', detail: 'full reconcile ran \u00b7 nothing lost', time: '02:31', unread: false }, + { id: 7, day: 'Yesterday', type: 'milestone', icon: 'milestone', tone: 'ok', text: 'P2 \u2014 Scheduler closed two days early', detail: 'the calibration noticed. So did I.', time: '09:40', unread: false }, + ], + issueDetail: { + 87: { + state: 'steeping', + assignee: 'Stephen', + milestone: 'Beta', + body: 'Squash-merges emit events out of order when CI runs long: the PR-closed webhook lands before the merge event, so lifecycle inference marks deploy before work-end and the actuals go negative. Regular merges infer correctly.', + comments: [ + { who: 'Ana K.', when: 'Feb 8 \u00b7 14:12', text: 'Repro: squash-merge only. Regular merges infer fine.' }, + { who: 'Stephen', when: 'Feb 9 \u00b7 09:30', text: 'Confirmed \u2014 the event order flips whenever CI takes more than ~5 minutes.' }, + ], + lifecycle: [ + { stage: 'Diagnosis', event: 'issue opened', when: 'Feb 2 \u00b7 09:14', icon: 'circle-dot', done: true }, + { stage: 'Triage', event: 'labeled est/2d \u00b7 milestoned Beta', when: 'Feb 3 \u00b7 10:02', icon: 'tag', done: true }, + { stage: 'Work start', event: 'first commit ref a41f09', when: 'Feb 6 \u00b7 11:47', icon: 'git-commit-horizontal', done: true }, + { stage: 'Deploy', event: 'PR merged', when: 'pending', icon: 'git-merge', done: false }, + { stage: 'Complete', event: 'issue closed', when: 'pending', icon: 'circle-check', done: false }, + ], + forecast: { p80: 'done Feb 11\u201313', note: 'from your est/2d history \u00b7 n=14' }, + blocks: [91, 92], + blockedBy: [], + note: 'It blocks #91 and #92. I\u2019d take it first \u2014 the critical path agrees with me.', + }, + }, + directives: { + pending: { + seq: 7, + who: 'Stephen', + when: 'today 09:12', + what: 'Pilots before calibration \u2014 push #78 to next week.', + diff: [ + { tone: 'info', change: '#78 Calibration store', from: 'this week', to: 'wk of Feb 23' }, + { tone: 'warn', change: 'Beta \u00b7 80% window', from: 'Mar 3\u201312', to: 'Mar 5\u201314' }, + { tone: 'ok', change: "Today's plan", from: '#87', to: '#87 \u00b7 unchanged' }, + ], + }, + entries: [ + { seq: 6, who: 'Stephen', when: 'Feb 8 \u00b7 16:40', what: 'Ana takes nothing new until #84 lands.', why: 'context thrash', status: 'applied', consequence: 'WIP capped \u00b7 v1.0 unmoved' }, + { seq: 5, who: 'Stephen', when: 'Feb 6 \u00b7 09:03', what: 'Ship Beta a week early.', why: 'board meeting', status: 'withdrawn', consequence: '80% would need scope \u22129d \u2014 withdrawn after diff' }, + { seq: 4, who: 'Stephen', when: 'Feb 3 \u00b7 11:21', what: 'deadline/hard on Pilot-ready.', why: 'contract date', status: 'applied', consequence: 'label applied \u00b7 runway flag raised' }, + { seq: 3, who: 'Stephen', when: 'Jan 28 \u00b7 08:47', what: 'Webhook work ahead of UI polish.', why: '', status: 'applied', consequence: '#91 +2 ranks \u00b7 Beta unmoved' }, + { seq: 2, who: 'Stephen', when: 'Jan 20 \u00b7 14:02', what: 'Estimates in days, never hours.', why: 'sanity', status: 'applied', consequence: 'label schema est/* confirmed' }, + { seq: 1, who: 'Stephen', when: 'Jan 19 \u00b7 09:00', what: 'CommiTea manages its own backlog.', why: 'dogfood', status: 'applied', consequence: 'stephen/commitea under management' }, + ], + }, + gantt: { + // day offsets from Feb 2; chart spans 42 days (Feb 2 – Mar 16) + days: 42, + weeks: [ + { at: 0, label: 'Feb 2' }, { at: 7, label: 'Feb 9' }, { at: 14, label: 'Feb 16' }, + { at: 21, label: 'Feb 23' }, { at: 28, label: 'Mar 2' }, { at: 35, label: 'Mar 9' }, { at: 41, label: 'Mar 15' }, + ], + today: 8, + band: { from: 29, to: 38, label: '80% · Mar 3–12' }, + due: { at: 41, label: 'Beta due' }, + rows: [ + { id: 71, title: 'Gitea client: token auth + retries', who: 'SM', state: 'done', start: 0, end: 4 }, + { id: 69, title: 'pm-state repo bootstrap', who: 'SM', state: 'done', start: 0, end: 1 }, + { id: 87, title: 'Fix lifecycle inference on merge events', who: 'SM', state: 'steeping', start: 4, end: 9, p80: 11, crit: true }, + { id: 92, title: 'Monte Carlo engine: percentile bands', who: 'SM', state: 'review', start: 9, end: 18, p80: 21, crit: true }, + { id: 91, title: 'Webhook listener: reconcile on reconnect', who: 'AK', state: 'scheduled', start: 9, end: 14, p80: 16 }, + { id: 99, title: 'Directive log: conflict-free merge test', who: 'SM', state: 'scheduled', start: 10, end: 11 }, + { id: 96, title: 'Label schema bootstrap for new repos', who: 'AK', state: 'scheduled', start: 14, end: 16 }, + { id: 84, title: 'Capacity model: focus factor per person', who: 'AK', state: 'scheduled', start: 18, end: 23, p80: 26, crit: true }, + { id: 102, title: 'Scheduler ignores standing allocation slices', who: 'SM', state: 'scheduled', start: 23, end: 25, p80: 28, crit: true }, + { id: 78, title: 'Calibration store cold-start distributions', who: 'SM', state: 'scheduled', start: 25, end: 30 }, + ], + }, + deps: { + nodes: [ + { id: 71, title: 'Gitea client: token auth + retries', tags: ['est/2d'], state: 'done', col: 0, row: 0.2 }, + { id: 69, title: 'pm-state repo bootstrap', tags: ['est/1d'], state: 'done', col: 0, row: 1.9 }, + { id: 87, title: 'Fix lifecycle inference on merge events', tags: ['est/2d', 'p/1'], state: 'steeping', days: '4d', col: 1, row: 0.7, rationale: 'It blocks #91 and #92. I\u2019d take it first \u2014 the critical path agrees with me.' }, + { id: 99, title: 'Directive log: conflict-free merge test', tags: ['est/1d', 'p/2'], state: 'triage', col: 1, row: 1.9 }, + { id: 91, title: 'Webhook listener: reconcile on reconnect', tags: ['est/3d', 'p/2'], state: 'triage', col: 2, row: 0 }, + { id: 92, title: 'Monte Carlo engine: percentile bands', tags: ['est/5d', 'p/1'], state: 'review', col: 2, row: 1.4 }, + { id: 84, title: 'Capacity model: focus factor per person', tags: ['est/3d', 'p/2'], state: 'steeping', days: '1d', col: 3, row: 1.4 }, + { id: 102, title: 'Scheduler ignores standing allocation slices', tags: ['p/2'], state: 'diagnosis', col: 4, row: 1.4 }, + ], + milestone: { name: 'Beta', due: 'Mar 15', col: 5, row: 1.4 }, + edges: [ + { from: 71, to: 87 }, + { from: 69, to: 99 }, + { from: 87, to: 91 }, + { from: 87, to: 92, crit: true }, + { from: 92, to: 84, crit: true }, + { from: 84, to: 102, crit: true }, + { from: 102, to: 'ms', crit: true }, + ], + critical: [87, 92, 84, 102], + unattached: [78, 96, 103, 65], + }, +}; diff --git a/docs/design/ui_kits/app/index.html b/docs/design/ui_kits/app/index.html new file mode 100644 index 0000000..f6ce6e6 --- /dev/null +++ b/docs/design/ui_kits/app/index.html @@ -0,0 +1,43 @@ + + + + + + +CommiTea + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..ce7676c --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "commitea", + "private": true, + "packageManager": "yarn@4.5.0", + "workspaces": [ + "apps/*", + "packages/*" + ], + "scripts": { + "dev": "yarn workspace @commitea/desktop dev", + "build": "yarn workspaces foreach -A run build", + "test": "yarn workspaces foreach -A run test", + "typecheck": "yarn workspaces foreach -A run typecheck" + } +} diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..424285f --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,16 @@ +{ + "name": "@commitea/core", + "private": true, + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "test": "vitest run", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "typescript": "^5.7.3", + "vitest": "^3.0.5" + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000..82f0f7a --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,10 @@ +export { + ESTIMATE_LABELS, + PRIORITY_LABELS, + HARD_DEADLINE_LABEL, + extractLabelFacts, + isCommiteaLabel, + parseEstimateLabel, + parsePriorityLabel, +} from './labels/label-schema.js' +export type { EstimateLabel, LabelFacts, PriorityLabel } from './labels/label-schema.js' diff --git a/packages/core/src/labels/label-schema.test.ts b/packages/core/src/labels/label-schema.test.ts new file mode 100644 index 0000000..a596eb5 --- /dev/null +++ b/packages/core/src/labels/label-schema.test.ts @@ -0,0 +1,97 @@ +import { describe, expect, it } from 'vitest' + +import { + ESTIMATE_LABELS, + extractLabelFacts, + parseEstimateLabel, + parsePriorityLabel, +} from './label-schema.js' + +describe('parseEstimateLabel', () => { + it.each([ + ['est/1d', 1], + ['est/2d', 2], + ['est/3d', 3], + ['est/5d', 5], + ['est/8d', 8], + ])('parses %s → %i days', (label, days) => { + expect(parseEstimateLabel(label)).toBe(days) + }) + + it('rejects estimates outside the fixed set', () => { + expect(parseEstimateLabel('est/4d')).toBeNull() + expect(parseEstimateLabel('est/13d')).toBeNull() + expect(parseEstimateLabel('est/3h')).toBeNull() + expect(parseEstimateLabel('EST/3d')).toBeNull() + }) +}) + +describe('parsePriorityLabel', () => { + it('parses p/1 through p/4', () => { + expect(parsePriorityLabel('p/1')).toBe(1) + expect(parsePriorityLabel('p/4')).toBe(4) + }) + + it('rejects out-of-range and malformed priorities', () => { + expect(parsePriorityLabel('p/0')).toBeNull() + expect(parsePriorityLabel('p/5')).toBeNull() + expect(parsePriorityLabel('P/1')).toBeNull() + expect(parsePriorityLabel('priority/1')).toBeNull() + }) +}) + +describe('extractLabelFacts', () => { + it('extracts all three axes from a full label set', () => { + const facts = extractLabelFacts(['est/3d', 'p/2', 'deadline/hard', 'bug']) + expect(facts).toEqual({ + estimateDays: 3, + priority: 2, + hardDeadline: true, + malformed: [], + conflicts: [], + }) + }) + + it('ignores labels outside CommiTea namespaces entirely', () => { + const facts = extractLabelFacts(['bug', 'enhancement', 'wontfix']) + expect(facts.estimateDays).toBeNull() + expect(facts.priority).toBeNull() + expect(facts.malformed).toEqual([]) + }) + + it('reports in-namespace labels that fail to parse as malformed', () => { + const facts = extractLabelFacts(['est/4d', 'p/9', 'deadline/soft']) + expect(facts.malformed).toEqual(['est/4d', 'p/9', 'deadline/soft']) + expect(facts.estimateDays).toBeNull() + expect(facts.priority).toBeNull() + expect(facts.hardDeadline).toBe(false) + }) + + it('resolves estimate conflicts pessimistically and reports them', () => { + const facts = extractLabelFacts(['est/2d', 'est/8d']) + expect(facts.estimateDays).toBe(8) + expect(facts.conflicts).toEqual(['est/8d']) + }) + + it('resolves priority conflicts toward urgency and reports them', () => { + const facts = extractLabelFacts(['p/3', 'p/1']) + expect(facts.priority).toBe(1) + expect(facts.conflicts).toEqual(['p/1']) + }) + + it('handles the empty label list', () => { + expect(extractLabelFacts([])).toEqual({ + estimateDays: null, + priority: null, + hardDeadline: false, + malformed: [], + conflicts: [], + }) + }) + + it('keeps the fixed estimate set in sync with its parser', () => { + for (const label of ESTIMATE_LABELS) { + expect(parseEstimateLabel(label)).not.toBeNull() + } + }) +}) diff --git a/packages/core/src/labels/label-schema.ts b/packages/core/src/labels/label-schema.ts new file mode 100644 index 0000000..6e8f585 --- /dev/null +++ b/packages/core/src/labels/label-schema.ts @@ -0,0 +1,91 @@ +/** + * The gitea label schema — CommiTea's only footprint in managed work repos. + * Fixed sets by design (docs/PLAN.md): estimates are coarse on purpose, the + * scheduler's calibration layer refines them; anything outside these sets is + * someone else's label and none of our business. + */ + +export const ESTIMATE_LABELS = ['est/1d', 'est/2d', 'est/3d', 'est/5d', 'est/8d'] as const +export const PRIORITY_LABELS = ['p/1', 'p/2', 'p/3', 'p/4'] as const +export const HARD_DEADLINE_LABEL = 'deadline/hard' + +export type EstimateLabel = (typeof ESTIMATE_LABELS)[number] +export type PriorityLabel = (typeof PRIORITY_LABELS)[number] + +const COMMITEA_NAMESPACES = ['est/', 'p/', 'deadline/'] as const + +export interface LabelFacts { + /** Days from an `est/*` label; null when absent. Largest wins on conflict. */ + estimateDays: number | null + /** 1 (most urgent) … 4 from a `p/*` label; null when absent. Most urgent wins on conflict. */ + priority: number | null + hardDeadline: boolean + /** Labels inside CommiTea namespaces that don't match the fixed sets. */ + malformed: string[] + /** Valid CommiTea labels that duplicated an axis (e.g. two `est/*` labels). */ + conflicts: string[] +} + +export function parseEstimateLabel(label: string): number | null { + if (!(ESTIMATE_LABELS as readonly string[]).includes(label)) return null + return Number(/^est\/(\d+)d$/.exec(label)![1]) +} + +export function parsePriorityLabel(label: string): number | null { + if (!(PRIORITY_LABELS as readonly string[]).includes(label)) return null + return Number(label.slice(2)) +} + +export function isCommiteaLabel(label: string): boolean { + return COMMITEA_NAMESPACES.some((ns) => label.startsWith(ns)) +} + +/** + * Reduce an issue's label list to scheduler inputs. Conflicting labels on one + * axis resolve pessimistically (largest estimate) / urgently (lowest priority + * number) and are reported in `conflicts` so the agent can nag about them. + */ +export function extractLabelFacts(labels: readonly string[]): LabelFacts { + const facts: LabelFacts = { + estimateDays: null, + priority: null, + hardDeadline: false, + malformed: [], + conflicts: [], + } + + for (const label of labels) { + if (!isCommiteaLabel(label)) continue + + if (label === HARD_DEADLINE_LABEL) { + facts.hardDeadline = true + continue + } + + const estimate = parseEstimateLabel(label) + if (estimate !== null) { + if (facts.estimateDays !== null) { + facts.conflicts.push(label) + facts.estimateDays = Math.max(facts.estimateDays, estimate) + } else { + facts.estimateDays = estimate + } + continue + } + + const priority = parsePriorityLabel(label) + if (priority !== null) { + if (facts.priority !== null) { + facts.conflicts.push(label) + facts.priority = Math.min(facts.priority, priority) + } else { + facts.priority = priority + } + continue + } + + facts.malformed.push(label) + } + + return facts +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..48633a9 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022"] + }, + "include": ["src"] +} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..4104ddc --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..67e76e1 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,3350 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@alloc/quick-lru@npm:^5.2.0": + version: 5.2.0 + resolution: "@alloc/quick-lru@npm:5.2.0" + checksum: 10c0/7b878c48b9d25277d0e1a9b8b2f2312a314af806b4129dc902f2bc29ab09b58236e53964689feec187b28c80d2203aff03829754773a707a8a5987f1b7682d92 + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/code-frame@npm:7.29.7" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.29.7" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/169fc2080169a40c1760155eaaaf739bcb882df0bec76a83adbda5493645bc17270a3434b8848c494b1933e96fe1d147370001e3cda09a39f43ae30f08ef2069 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/compat-data@npm:7.29.7" + checksum: 10c0/47913f05e08a45a1c9df38c02b4b49e391005085b489432647a1abe112e5d9c75e3be8ea5972b7f6da4ec5d1339922ceb9ea02b8a25d4ed1cb8636e5261f344e + languageName: node + linkType: hard + +"@babel/core@npm:^7.26.10, @babel/core@npm:^7.28.0": + version: 7.29.7 + resolution: "@babel/core@npm:7.29.7" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.7" + "@babel/helper-compilation-targets": "npm:^7.29.7" + "@babel/helper-module-transforms": "npm:^7.29.7" + "@babel/helpers": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/template": "npm:^7.29.7" + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + "@jridgewell/remapping": "npm:^2.3.5" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/112fb09c24de7a1de64d1de2c31fe65c4e6af4cb2fb6e6d99ea5373e6fc51e75b88581c0efae4c4c68f119a02a988c7106e95011a41530a2fb8ed793c7eaa07b + languageName: node + linkType: hard + +"@babel/generator@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/generator@npm:7.29.7" + dependencies: + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/9bf72b01b5bd0ea5b1288a0e37dbd360bff2f2b1ce73342c0d40fb3db2ec3dc004ada5ffa925c5e12939a416eed59e600d562b8ecd938ce0d27dfd0eb6c6c2b7 + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-compilation-targets@npm:7.29.7" + dependencies: + "@babel/compat-data": "npm:^7.29.7" + "@babel/helper-validator-option": "npm:^7.29.7" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/4c15fd4c69a0a7047799a28a88460c19cede0a0ee8af994ea169114986f4af48b92c7393a4a3fee0456c11a656eece3448a6ed06354453d6c27cccf17195453b + languageName: node + linkType: hard + +"@babel/helper-globals@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-globals@npm:7.29.7" + checksum: 10c0/f38417c40b1129a1b2b519ca961b9040c8827d1444fd74068702286b91b77089431dc76b6b9d5c1496e5da2a4f3ad329c6946e688ba3fa0d1d0b3d2b4f34f36a + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-module-imports@npm:7.29.7" + dependencies: + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/6adf60d97356027413342a092f818d9678c4f5caff716a33e3284b5ae14e47a9e88059d421dde4ee4894691260039a12602c0e7becadc175602194b40dfa345d + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-module-transforms@npm:7.29.7" + dependencies: + "@babel/helper-module-imports": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + "@babel/traverse": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/ee5a2172c24a42be696836f4b0d947489c9729d8adf5821885cf77d1ad5333e3c447368e9a71f67df1099570490553dccf9f888ef0a92a48aa63cb086bd8c7e1 + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-plugin-utils@npm:7.29.7" + checksum: 10c0/380477a06133274a2759f9355929cb60a95e8b8fee624a1ae1fa349e1d1645b89daca456f72833f6d1062bffa12ee4271c5bf0cc5a61c0166cdc24c7591e2408 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-string-parser@npm:7.29.7" + checksum: 10c0/194bc0f1716e396d5ffde56ad6119745fb9557662c98611590e5e454906783a4ccb21ce93056b8eb69a4909044834e45d96e50ac695bbe9e3221648fe033c06c + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-identifier@npm:7.29.7" + checksum: 10c0/4795354e7ae0dcafa72de1cd04ec51252dc1498517170beaf019e03effc5b7bf13c6b21a3949a77e07b8125be7f106ed1131350d8ebd4566ae874094a726d62b + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-option@npm:7.29.7" + checksum: 10c0/d2a06c6d0ac40ba4a2f219fc2cab249c7a94bacdb2686273b7f9598571c908809b48468ff588915a346e6cc7296f60b581023d1d498b747fed06f779d335c2cc + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helpers@npm:7.29.7" + dependencies: + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/218e8d10953647c9f44775f5a022b227a182674853b5ea8631889deb7e1a3e4bc870388aaecf59bb8bd92a87f9a96220ed3f70a35bffec6bcf9169ecb67891ac + languageName: node + linkType: hard + +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/parser@npm:7.29.7" + dependencies: + "@babel/types": "npm:^7.29.7" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/65133038f80b54a714d6027cb77cee3f9a6b5c4c6842ce674301e13947cbcbfa8055e63acaf1b84c085d34226a14425b2c2b97b829e0e226d2e8f1299942a51d + languageName: node + linkType: hard + +"@babel/plugin-transform-arrow-functions@npm:^7.25.9": + version: 7.29.7 + resolution: "@babel/plugin-transform-arrow-functions@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/03405abac83122b760c4d688a256c7a67961fd5a4396dfd119cf89a118984d31add38eeace38a158c63c3a4257a644e15da8836ee9e50876bf6876e988060be2 + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx-self@npm:^7.27.1": + version: 7.29.7 + resolution: "@babel/plugin-transform-react-jsx-self@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/288995f0fd0d61ab740a315fb56c8255eb87dd4a4ac2ac7d0fdd4ce173c3878200141e80da2db0e598c7b2a71e74e604afdbb4c8e14ae6e0527ce0b6294c03da + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx-source@npm:^7.27.1": + version: 7.29.7 + resolution: "@babel/plugin-transform-react-jsx-source@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/a121899631e6d99b9e1b276acf736dbb77948a31f8eeeae67b89c8a4ab0f05e51ba64544baa06c286a2b9944f227244e15aac464e2313d286d0511fe51e27975 + languageName: node + linkType: hard + +"@babel/template@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/template@npm:7.29.7" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/8bb7f900dcab0e9e1c5ffbc33ca10e0d26b7b2e2ca804becb73ee771b9c4ed6e2908a4ae4a14c08560febb45d2b6b9a173955e42ad404d05f8b04840a14d9c58 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/traverse@npm:7.29.7" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.7" + "@babel/helper-globals": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + debug: "npm:^4.3.1" + checksum: 10c0/e256a1fbdb956555b76f3c285b1e453f6bedec8b3afb61751d99d933efd11c7d79caf5ddf2493570058a9f7deaa1b48324380d7c1aa1443fd9508becbf56331a + languageName: node + linkType: hard + +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.28.2, @babel/types@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/types@npm:7.29.7" + dependencies: + "@babel/helper-string-parser": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + checksum: 10c0/b6623994c69717fa27294f5fa46d59140338e2d86c6c1c13085c84ef7d53086ee357fbf4fe9abe3dd3da75734dc77c4c0df2f90fb29e667558bb3b3fb705e88f + languageName: node + linkType: hard + +"@commitea/core@workspace:*, @commitea/core@workspace:packages/core": + version: 0.0.0-use.local + resolution: "@commitea/core@workspace:packages/core" + dependencies: + typescript: "npm:^5.7.3" + vitest: "npm:^3.0.5" + languageName: unknown + linkType: soft + +"@commitea/desktop@workspace:apps/desktop": + version: 0.0.0-use.local + resolution: "@commitea/desktop@workspace:apps/desktop" + dependencies: + "@commitea/core": "workspace:*" + "@types/node": "npm:^22.13.1" + "@types/react": "npm:^18.3.18" + "@types/react-dom": "npm:^18.3.5" + "@vitejs/plugin-react": "npm:^4.3.4" + autoprefixer: "npm:^10.4.20" + electron: "npm:^34.0.0" + electron-vite: "npm:^3.1.0" + postcss: "npm:^8.5.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + tailwindcss: "npm:^3.4.17" + typescript: "npm:^5.7.3" + vite: "npm:^6.1.0" + languageName: unknown + linkType: soft + +"@electron/get@npm:^2.0.0": + version: 2.0.3 + resolution: "@electron/get@npm:2.0.3" + dependencies: + debug: "npm:^4.1.1" + env-paths: "npm:^2.2.0" + fs-extra: "npm:^8.1.0" + global-agent: "npm:^3.0.0" + got: "npm:^11.8.5" + progress: "npm:^2.0.3" + semver: "npm:^6.2.0" + sumchecker: "npm:^3.0.1" + dependenciesMeta: + global-agent: + optional: true + checksum: 10c0/148957d531bac50c29541515f2483c3e5c9c6ba9f0269a5d536540d2b8d849188a89588f18901f3a84c2b4fd376d1e0c5ea2159eb2d17bda68558f57df19015e + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/aix-ppc64@npm:0.25.12" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/aix-ppc64@npm:0.28.1" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-arm64@npm:0.25.12" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/android-arm64@npm:0.28.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-arm@npm:0.25.12" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/android-arm@npm:0.28.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-x64@npm:0.25.12" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/android-x64@npm:0.28.1" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/darwin-arm64@npm:0.25.12" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/darwin-arm64@npm:0.28.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/darwin-x64@npm:0.25.12" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/darwin-x64@npm:0.28.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/freebsd-arm64@npm:0.25.12" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/freebsd-arm64@npm:0.28.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/freebsd-x64@npm:0.25.12" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/freebsd-x64@npm:0.28.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-arm64@npm:0.25.12" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-arm64@npm:0.28.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-arm@npm:0.25.12" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-arm@npm:0.28.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-ia32@npm:0.25.12" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-ia32@npm:0.28.1" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-loong64@npm:0.25.12" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-loong64@npm:0.28.1" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-mips64el@npm:0.25.12" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-mips64el@npm:0.28.1" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-ppc64@npm:0.25.12" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-ppc64@npm:0.28.1" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-riscv64@npm:0.25.12" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-riscv64@npm:0.28.1" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-s390x@npm:0.25.12" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-s390x@npm:0.28.1" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-x64@npm:0.25.12" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-x64@npm:0.28.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/netbsd-arm64@npm:0.25.12" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/netbsd-arm64@npm:0.28.1" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/netbsd-x64@npm:0.25.12" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/netbsd-x64@npm:0.28.1" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openbsd-arm64@npm:0.25.12" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/openbsd-arm64@npm:0.28.1" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openbsd-x64@npm:0.25.12" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/openbsd-x64@npm:0.28.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openharmony-arm64@npm:0.25.12" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/openharmony-arm64@npm:0.28.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/sunos-x64@npm:0.25.12" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/sunos-x64@npm:0.28.1" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-arm64@npm:0.25.12" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/win32-arm64@npm:0.28.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-ia32@npm:0.25.12" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/win32-ia32@npm:0.28.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-x64@npm:0.25.12" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/win32-x64@npm:0.28.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.2, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b + languageName: node + linkType: hard + +"@jridgewell/remapping@npm:^2.3.5": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.28": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:1.0.0-beta.27": + version: 1.0.0-beta.27 + resolution: "@rolldown/pluginutils@npm:1.0.0-beta.27" + checksum: 10c0/9658f235b345201d4f6bfb1f32da9754ca164f892d1cb68154fe5f53c1df42bd675ecd409836dff46884a7847d6c00bdc38af870f7c81e05bba5c2645eb4ab9c + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.62.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-android-arm64@npm:4.62.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-darwin-arm64@npm:4.62.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-darwin-x64@npm:4.62.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.62.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-freebsd-x64@npm:4.62.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.62.2" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.62.2" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.62.2" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.62.2" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.62.2" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.62.2" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.62.2" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.62.2" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.62.2" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.62.2" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.62.2" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.62.2" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.62.2" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-openbsd-x64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-openbsd-x64@npm:4.62.2" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.62.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.62.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.62.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.62.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.62.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@sindresorhus/is@npm:^4.0.0": + version: 4.6.0 + resolution: "@sindresorhus/is@npm:4.6.0" + checksum: 10c0/33b6fb1d0834ec8dd7689ddc0e2781c2bfd8b9c4e4bacbcb14111e0ae00621f2c264b8a7d36541799d74888b5dccdf422a891a5cb5a709ace26325eedc81e22e + languageName: node + linkType: hard + +"@szmarczak/http-timer@npm:^4.0.5": + version: 4.0.6 + resolution: "@szmarczak/http-timer@npm:4.0.6" + dependencies: + defer-to-connect: "npm:^2.0.0" + checksum: 10c0/73946918c025339db68b09abd91fa3001e87fc749c619d2e9c2003a663039d4c3cb89836c98a96598b3d47dec2481284ba85355392644911f5ecd2336536697f + languageName: node + linkType: hard + +"@types/babel__core@npm:^7.20.5": + version: 7.20.5 + resolution: "@types/babel__core@npm:7.20.5" + dependencies: + "@babel/parser": "npm:^7.20.7" + "@babel/types": "npm:^7.20.7" + "@types/babel__generator": "npm:*" + "@types/babel__template": "npm:*" + "@types/babel__traverse": "npm:*" + checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff + languageName: node + linkType: hard + +"@types/babel__generator@npm:*": + version: 7.27.0 + resolution: "@types/babel__generator@npm:7.27.0" + dependencies: + "@babel/types": "npm:^7.0.0" + checksum: 10c0/9f9e959a8792df208a9d048092fda7e1858bddc95c6314857a8211a99e20e6830bdeb572e3587ae8be5429e37f2a96fcf222a9f53ad232f5537764c9e13a2bbd + languageName: node + linkType: hard + +"@types/babel__template@npm:*": + version: 7.4.4 + resolution: "@types/babel__template@npm:7.4.4" + dependencies: + "@babel/parser": "npm:^7.1.0" + "@babel/types": "npm:^7.0.0" + checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b + languageName: node + linkType: hard + +"@types/babel__traverse@npm:*": + version: 7.28.0 + resolution: "@types/babel__traverse@npm:7.28.0" + dependencies: + "@babel/types": "npm:^7.28.2" + checksum: 10c0/b52d7d4e8fc6a9018fe7361c4062c1c190f5778cf2466817cb9ed19d69fbbb54f9a85ffedeb748ed8062d2cf7d4cc088ee739848f47c57740de1c48cbf0d0994 + languageName: node + linkType: hard + +"@types/cacheable-request@npm:^6.0.1": + version: 6.0.3 + resolution: "@types/cacheable-request@npm:6.0.3" + dependencies: + "@types/http-cache-semantics": "npm:*" + "@types/keyv": "npm:^3.1.4" + "@types/node": "npm:*" + "@types/responselike": "npm:^1.0.0" + checksum: 10c0/10816a88e4e5b144d43c1d15a81003f86d649776c7f410c9b5e6579d0ad9d4ca71c541962fb403077388b446e41af7ae38d313e46692144985f006ac5e11fa03 + languageName: node + linkType: hard + +"@types/chai@npm:^5.2.2": + version: 5.2.3 + resolution: "@types/chai@npm:5.2.3" + dependencies: + "@types/deep-eql": "npm:*" + assertion-error: "npm:^2.0.1" + checksum: 10c0/e0ef1de3b6f8045a5e473e867c8565788c444271409d155588504840ad1a53611011f85072188c2833941189400228c1745d78323dac13fcede9c2b28bacfb2f + languageName: node + linkType: hard + +"@types/deep-eql@npm:*": + version: 4.0.2 + resolution: "@types/deep-eql@npm:4.0.2" + checksum: 10c0/bf3f811843117900d7084b9d0c852da9a044d12eb40e6de73b552598a6843c21291a8a381b0532644574beecd5e3491c5ff3a0365ab86b15d59862c025384844 + languageName: node + linkType: hard + +"@types/estree@npm:1.0.9, @types/estree@npm:^1.0.0": + version: 1.0.9 + resolution: "@types/estree@npm:1.0.9" + checksum: 10c0/3ad3286ca2988cd550dafb8f2ad599c8474868e954fa601a36655bdfefd8039f7c714b8c1c7f2ae219ffbd58bd4660e66fa7479a0120fc02d4777057d4865387 + languageName: node + linkType: hard + +"@types/http-cache-semantics@npm:*": + version: 4.2.0 + resolution: "@types/http-cache-semantics@npm:4.2.0" + checksum: 10c0/82dd33cbe7d4843f1e884a251c6a12d385b62274353b9db167462e7fbffdbb3a83606f9952203017c5b8cabbd7b9eef0cf240a3a9dedd20f69875c9701939415 + languageName: node + linkType: hard + +"@types/keyv@npm:^3.1.4": + version: 3.1.4 + resolution: "@types/keyv@npm:3.1.4" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/ff8f54fc49621210291f815fe5b15d809fd7d032941b3180743440bd507ecdf08b9e844625fa346af568c84bf34114eb378dcdc3e921a08ba1e2a08d7e3c809c + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 26.1.0 + resolution: "@types/node@npm:26.1.0" + dependencies: + undici-types: "npm:~8.3.0" + checksum: 10c0/61c22ad1ef215e24138cb8b7368fb68bab9de4c123b3f23d411749b015ba1b7d22f878ad54add26a0b69068d7e07c04af665069d8571b6c6c3b9b2fb73b7bd91 + languageName: node + linkType: hard + +"@types/node@npm:^20.9.0": + version: 20.19.43 + resolution: "@types/node@npm:20.19.43" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10c0/9bcec3b5295bdd77ff0b44a528a69f7e22028c347507ba2c69be47ec84e30299f45043b222e9c86c510e138c9c53b2419dd5cd34920602a4a5a381c288075318 + languageName: node + linkType: hard + +"@types/node@npm:^22.13.1": + version: 22.20.0 + resolution: "@types/node@npm:22.20.0" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10c0/55d78223205bd5f81f043d71b7a5c8d8854b9ef44ef81291680943adb27fa5ba1f092658c87183d5bc8cf6baf6a57b81dad966eb3afa452cc301a615b6d9b20e + languageName: node + linkType: hard + +"@types/prop-types@npm:*": + version: 15.7.15 + resolution: "@types/prop-types@npm:15.7.15" + checksum: 10c0/b59aad1ad19bf1733cf524fd4e618196c6c7690f48ee70a327eb450a42aab8e8a063fbe59ca0a5701aebe2d92d582292c0fb845ea57474f6a15f6994b0e260b2 + languageName: node + linkType: hard + +"@types/react-dom@npm:^18.3.5": + version: 18.3.7 + resolution: "@types/react-dom@npm:18.3.7" + peerDependencies: + "@types/react": ^18.0.0 + checksum: 10c0/8bd309e2c3d1604a28a736a24f96cbadf6c05d5288cfef8883b74f4054c961b6b3a5e997fd5686e492be903c8f3380dba5ec017eff3906b1256529cd2d39603e + languageName: node + linkType: hard + +"@types/react@npm:^18.3.18": + version: 18.3.31 + resolution: "@types/react@npm:18.3.31" + dependencies: + "@types/prop-types": "npm:*" + csstype: "npm:^3.2.2" + checksum: 10c0/44180549dd045f536ececd39e39aacdf828e76adc1c4a90b132f453e23cc370c4648d9102ae401172ebd8fd8b1977a901a39e214e53ec77171b27514b588c179 + languageName: node + linkType: hard + +"@types/responselike@npm:^1.0.0": + version: 1.0.3 + resolution: "@types/responselike@npm:1.0.3" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/a58ba341cb9e7d74f71810a88862da7b2a6fa42e2a1fc0ce40498f6ea1d44382f0640117057da779f74c47039f7166bf48fad02dc876f94e005c7afa50f5e129 + languageName: node + linkType: hard + +"@types/yauzl@npm:^2.9.1": + version: 2.10.3 + resolution: "@types/yauzl@npm:2.10.3" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/f1b7c1b99fef9f2fe7f1985ef7426d0cebe48cd031f1780fcdc7451eec7e31ac97028f16f50121a59bcf53086a1fc8c856fd5b7d3e00970e43d92ae27d6b43dc + languageName: node + linkType: hard + +"@vitejs/plugin-react@npm:^4.3.4": + version: 4.7.0 + resolution: "@vitejs/plugin-react@npm:4.7.0" + dependencies: + "@babel/core": "npm:^7.28.0" + "@babel/plugin-transform-react-jsx-self": "npm:^7.27.1" + "@babel/plugin-transform-react-jsx-source": "npm:^7.27.1" + "@rolldown/pluginutils": "npm:1.0.0-beta.27" + "@types/babel__core": "npm:^7.20.5" + react-refresh: "npm:^0.17.0" + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + checksum: 10c0/692f23960972879485d647713663ec299c478222c96567d60285acf7c7dc5c178e71abfe9d2eefddef1eeb01514dacbc2ed68aad84628debf9c7116134734253 + languageName: node + linkType: hard + +"@vitest/expect@npm:3.2.7": + version: 3.2.7 + resolution: "@vitest/expect@npm:3.2.7" + dependencies: + "@types/chai": "npm:^5.2.2" + "@vitest/spy": "npm:3.2.7" + "@vitest/utils": "npm:3.2.7" + chai: "npm:^5.2.0" + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/5a13fee261d1020d47a3517f4d9a2cb209d7475399c815f6ebe22be116ddb0c6c401592f07d24f9a8b1c192155d11f651397ee28061f886ea61f1f9181a8fd48 + languageName: node + linkType: hard + +"@vitest/mocker@npm:3.2.7": + version: 3.2.7 + resolution: "@vitest/mocker@npm:3.2.7" + dependencies: + "@vitest/spy": "npm:3.2.7" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.17" + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/e8d9a155723e77b3e14c5a1eba35d8966b94420ebc733ffd7119b7bede22006580c468eec2aec6612fb77852cbcdfa94c6f49218f3ca1427aa9363d545c624fd + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:3.2.7, @vitest/pretty-format@npm:^3.2.7": + version: 3.2.7 + resolution: "@vitest/pretty-format@npm:3.2.7" + dependencies: + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/f556dd5f9e5240c7ab054d795622292c1b23f6aad3332d1e250f33d801783efbb7883387640b76d22cc91b81f30cb735b40371ec3e4f5293e735495f45d624df + languageName: node + linkType: hard + +"@vitest/runner@npm:3.2.7": + version: 3.2.7 + resolution: "@vitest/runner@npm:3.2.7" + dependencies: + "@vitest/utils": "npm:3.2.7" + pathe: "npm:^2.0.3" + strip-literal: "npm:^3.0.0" + checksum: 10c0/d73ba6df95175ea2b545d37fde3e743d113ecd608693b444af1d4dc27956abe4d5b500e25e09ffe46ce720de6b7cec68c264fca20366fc6da0d9ce75c52047cc + languageName: node + linkType: hard + +"@vitest/snapshot@npm:3.2.7": + version: 3.2.7 + resolution: "@vitest/snapshot@npm:3.2.7" + dependencies: + "@vitest/pretty-format": "npm:3.2.7" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + checksum: 10c0/c58a17a2c98788e77d30d39837c024852eca91618efe9b53ec399cf76332365b8cc592a69617e0f7d696df716dcc341dac61c6cbea86ae98cdb49a4a84f48d19 + languageName: node + linkType: hard + +"@vitest/spy@npm:3.2.7": + version: 3.2.7 + resolution: "@vitest/spy@npm:3.2.7" + dependencies: + tinyspy: "npm:^4.0.3" + checksum: 10c0/cf2728b4ddc6137e0ca749215c7714845e8221a5f799e933bc623216a9958fd8b032cdd7f033ed8375df9e6ed84a18b64687ca2530f50e8a76bc3b1d16d88c42 + languageName: node + linkType: hard + +"@vitest/utils@npm:3.2.7": + version: 3.2.7 + resolution: "@vitest/utils@npm:3.2.7" + dependencies: + "@vitest/pretty-format": "npm:3.2.7" + loupe: "npm:^3.1.4" + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/fea57d6cf66853926f54ea6e9bd249b707120b0abba565d96a3880c7f04d5c5cd4778546879b9074cc73c0b81b475c1873791d4b3b3e5a324115b5bcfd3a46bb + languageName: node + linkType: hard + +"abbrev@npm:^5.0.0": + version: 5.0.0 + resolution: "abbrev@npm:5.0.0" + checksum: 10c0/8e88f5c798ea4562d28c5a3e9ad69e3879890bc5d695d8f2dffb8609be4c890aacc8f80ef4553fdd2c6a62d70c2ce8bc57b38074e383beb7487bdafa9ed42ea5 + languageName: node + linkType: hard + +"any-promise@npm:^1.0.0": + version: 1.3.0 + resolution: "any-promise@npm:1.3.0" + checksum: 10c0/60f0298ed34c74fef50daab88e8dab786036ed5a7fad02e012ab57e376e0a0b4b29e83b95ea9b5e7d89df762f5f25119b83e00706ecaccb22cfbacee98d74889 + languageName: node + linkType: hard + +"anymatch@npm:~3.1.2": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac + languageName: node + linkType: hard + +"arg@npm:^5.0.2": + version: 5.0.2 + resolution: "arg@npm:5.0.2" + checksum: 10c0/ccaf86f4e05d342af6666c569f844bec426595c567d32a8289715087825c2ca7edd8a3d204e4d2fb2aa4602e09a57d0c13ea8c9eea75aac3dbb4af5514e6800e + languageName: node + linkType: hard + +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + +"autoprefixer@npm:^10.4.20": + version: 10.5.2 + resolution: "autoprefixer@npm:10.5.2" + dependencies: + browserslist: "npm:^4.28.4" + caniuse-lite: "npm:^1.0.30001799" + fraction.js: "npm:^5.3.4" + picocolors: "npm:^1.1.1" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.1.0 + bin: + autoprefixer: bin/autoprefixer + checksum: 10c0/588fec73c061da8a95f6a7f4f0cde811a581087852325564d2d8f15c9356b204db50c1566c57e06dc0b1bcdf2b8ff10f85e89ef08d94c10f0e5ba187a4c52690 + languageName: node + linkType: hard + +"baseline-browser-mapping@npm:^2.10.42": + version: 2.10.42 + resolution: "baseline-browser-mapping@npm:2.10.42" + bin: + baseline-browser-mapping: dist/cli.cjs + checksum: 10c0/4a9f54818d17d8dbee69096563b2cd5e2175f66752c479f1c10d31e072c1c2b7241a9bdaee78d5236178c581ca2735e75fbb0d0877d6492958eed9f6e46e03f3 + languageName: node + linkType: hard + +"binary-extensions@npm:^2.0.0": + version: 2.3.0 + resolution: "binary-extensions@npm:2.3.0" + checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 + languageName: node + linkType: hard + +"boolean@npm:^3.0.1": + version: 3.2.0 + resolution: "boolean@npm:3.2.0" + checksum: 10c0/6a0dc9668f6f3dda42a53c181fcbdad223169c8d87b6c4011b87a8b14a21770efb2934a778f063d7ece17280f8c06d313c87f7b834bb1dd526a867ffcd00febf + languageName: node + linkType: hard + +"braces@npm:^3.0.3, braces@npm:~3.0.2": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 + languageName: node + linkType: hard + +"browserslist@npm:^4.24.0, browserslist@npm:^4.28.4": + version: 4.28.5 + resolution: "browserslist@npm:4.28.5" + dependencies: + baseline-browser-mapping: "npm:^2.10.42" + caniuse-lite: "npm:^1.0.30001800" + electron-to-chromium: "npm:^1.5.387" + node-releases: "npm:^2.0.50" + update-browserslist-db: "npm:^1.2.3" + bin: + browserslist: cli.js + checksum: 10c0/d6bb4ab286a7071db52cbeabd46ff816e09c9171d7e5da246f0b9489601ad3d4adb9fc3d14fa934a8646078f8a717507c0518a364128735a3b5a7875900b5a19 + languageName: node + linkType: hard + +"buffer-crc32@npm:~0.2.3": + version: 0.2.13 + resolution: "buffer-crc32@npm:0.2.13" + checksum: 10c0/cb0a8ddf5cf4f766466db63279e47761eb825693eeba6a5a95ee4ec8cb8f81ede70aa7f9d8aeec083e781d47154290eb5d4d26b3f7a465ec57fb9e7d59c47150 + languageName: node + linkType: hard + +"cac@npm:^6.7.14": + version: 6.7.14 + resolution: "cac@npm:6.7.14" + checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 + languageName: node + linkType: hard + +"cacheable-lookup@npm:^5.0.3": + version: 5.0.4 + resolution: "cacheable-lookup@npm:5.0.4" + checksum: 10c0/a6547fb4954b318aa831cbdd2f7b376824bc784fb1fa67610e4147099e3074726072d9af89f12efb69121415a0e1f2918a8ddd4aafcbcf4e91fbeef4a59cd42c + languageName: node + linkType: hard + +"cacheable-request@npm:^7.0.2": + version: 7.0.4 + resolution: "cacheable-request@npm:7.0.4" + dependencies: + clone-response: "npm:^1.0.2" + get-stream: "npm:^5.1.0" + http-cache-semantics: "npm:^4.0.0" + keyv: "npm:^4.0.0" + lowercase-keys: "npm:^2.0.0" + normalize-url: "npm:^6.0.1" + responselike: "npm:^2.0.0" + checksum: 10c0/0834a7d17ae71a177bc34eab06de112a43f9b5ad05ebe929bec983d890a7d9f2bc5f1aa8bb67ea2b65e07a3bc74bea35fa62dd36dbac52876afe36fdcf83da41 + languageName: node + linkType: hard + +"camelcase-css@npm:^2.0.1": + version: 2.0.1 + resolution: "camelcase-css@npm:2.0.1" + checksum: 10c0/1a1a3137e8a781e6cbeaeab75634c60ffd8e27850de410c162cce222ea331cd1ba5364e8fb21c95e5ca76f52ac34b81a090925ca00a87221355746d049c6e273 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001799, caniuse-lite@npm:^1.0.30001800": + version: 1.0.30001803 + resolution: "caniuse-lite@npm:1.0.30001803" + checksum: 10c0/71586e9c84633cf766b208448eb76f860ec6e3befffc626d1f004e1902da063a7ab96cc94d1f4b36c0324e12997b3325a726de3287edfec91d0df495627a1d43 + languageName: node + linkType: hard + +"chai@npm:^5.2.0": + version: 5.3.3 + resolution: "chai@npm:5.3.3" + dependencies: + assertion-error: "npm:^2.0.1" + check-error: "npm:^2.1.1" + deep-eql: "npm:^5.0.1" + loupe: "npm:^3.1.0" + pathval: "npm:^2.0.0" + checksum: 10c0/b360fd4d38861622e5010c2f709736988b05c7f31042305fa3f4e9911f6adb80ccfb4e302068bf8ed10e835c2e2520cba0f5edc13d878b886987e5aa62483f53 + languageName: node + linkType: hard + +"check-error@npm:^2.1.1": + version: 2.1.3 + resolution: "check-error@npm:2.1.3" + checksum: 10c0/878e99038fb6476316b74668cd6a498c7e66df3efe48158fa40db80a06ba4258742ac3ee2229c4a2a98c5e73f5dff84eb3e50ceb6b65bbd8f831eafc8338607d + languageName: node + linkType: hard + +"chokidar@npm:^3.6.0": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"clone-response@npm:^1.0.2": + version: 1.0.3 + resolution: "clone-response@npm:1.0.3" + dependencies: + mimic-response: "npm:^1.0.0" + checksum: 10c0/06a2b611824efb128810708baee3bd169ec9a1bf5976a5258cd7eb3f7db25f00166c6eee5961f075c7e38e194f373d4fdf86b8166ad5b9c7e82bbd2e333a6087 + languageName: node + linkType: hard + +"commander@npm:^4.0.0": + version: 4.1.1 + resolution: "commander@npm:4.1.1" + checksum: 10c0/84a76c08fe6cc08c9c93f62ac573d2907d8e79138999312c92d4155bc2325d487d64d13f669b2000c9f8caf70493c1be2dac74fec3c51d5a04f8bc3ae1830bab + languageName: node + linkType: hard + +"commitea@workspace:.": + version: 0.0.0-use.local + resolution: "commitea@workspace:." + languageName: unknown + linkType: soft + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"cssesc@npm:^3.0.0": + version: 3.0.0 + resolution: "cssesc@npm:3.0.0" + bin: + cssesc: bin/cssesc + checksum: 10c0/6bcfd898662671be15ae7827120472c5667afb3d7429f1f917737f3bf84c4176003228131b643ae74543f17a394446247df090c597bb9a728cce298606ed0aa7 + languageName: node + linkType: hard + +"csstype@npm:^3.2.2": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce + languageName: node + linkType: hard + +"debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.4.1": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"decompress-response@npm:^6.0.0": + version: 6.0.0 + resolution: "decompress-response@npm:6.0.0" + dependencies: + mimic-response: "npm:^3.1.0" + checksum: 10c0/bd89d23141b96d80577e70c54fb226b2f40e74a6817652b80a116d7befb8758261ad073a8895648a29cc0a5947021ab66705cb542fa9c143c82022b27c5b175e + languageName: node + linkType: hard + +"deep-eql@npm:^5.0.1": + version: 5.0.2 + resolution: "deep-eql@npm:5.0.2" + checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247 + languageName: node + linkType: hard + +"defer-to-connect@npm:^2.0.0": + version: 2.0.1 + resolution: "defer-to-connect@npm:2.0.1" + checksum: 10c0/625ce28e1b5ad10cf77057b9a6a727bf84780c17660f6644dab61dd34c23de3001f03cedc401f7d30a4ed9965c2e8a7336e220a329146f2cf85d4eddea429782 + languageName: node + linkType: hard + +"define-data-property@npm:^1.0.1": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.0.1" + checksum: 10c0/dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37 + languageName: node + linkType: hard + +"define-properties@npm:^1.2.1": + version: 1.2.1 + resolution: "define-properties@npm:1.2.1" + dependencies: + define-data-property: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.0" + object-keys: "npm:^1.1.1" + checksum: 10c0/88a152319ffe1396ccc6ded510a3896e77efac7a1bfbaa174a7b00414a1747377e0bb525d303794a47cf30e805c2ec84e575758512c6e44a993076d29fd4e6c3 + languageName: node + linkType: hard + +"detect-node@npm:^2.0.4": + version: 2.1.0 + resolution: "detect-node@npm:2.1.0" + checksum: 10c0/f039f601790f2e9d4654e499913259a798b1f5246ae24f86ab5e8bd4aaf3bce50484234c494f11fb00aecb0c6e2733aa7b1cf3f530865640b65fbbd65b2c4e09 + languageName: node + linkType: hard + +"didyoumean@npm:^1.2.2": + version: 1.2.2 + resolution: "didyoumean@npm:1.2.2" + checksum: 10c0/95d0b53d23b851aacff56dfadb7ecfedce49da4232233baecfeecb7710248c4aa03f0aa8995062f0acafaf925adf8536bd7044a2e68316fd7d411477599bc27b + languageName: node + linkType: hard + +"dlv@npm:^1.1.3": + version: 1.1.3 + resolution: "dlv@npm:1.1.3" + checksum: 10c0/03eb4e769f19a027fd5b43b59e8a05e3fd2100ac239ebb0bf9a745de35d449e2f25cfaf3aa3934664551d72856f4ae8b7822016ce5c42c2d27c18ae79429ec42 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.5.387": + version: 1.5.389 + resolution: "electron-to-chromium@npm:1.5.389" + checksum: 10c0/3681a3245fef5dfc8d9cdee231e80f8fe4fd7a87713d3a29a21ae2ec0cb6995cf14f3b3fd9e3fa8b483256e28ba2fa1daa9f23e93911a3a7241cd8f5daee9665 + languageName: node + linkType: hard + +"electron-vite@npm:^3.1.0": + version: 3.1.0 + resolution: "electron-vite@npm:3.1.0" + dependencies: + "@babel/core": "npm:^7.26.10" + "@babel/plugin-transform-arrow-functions": "npm:^7.25.9" + cac: "npm:^6.7.14" + esbuild: "npm:^0.25.1" + magic-string: "npm:^0.30.17" + picocolors: "npm:^1.1.1" + peerDependencies: + "@swc/core": ^1.0.0 + vite: ^4.0.0 || ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + "@swc/core": + optional: true + bin: + electron-vite: bin/electron-vite.js + checksum: 10c0/c5efacf83c869a933d7da390b3312beb47c145339e630f9d3ebbedbe3301ec2b070e4d05668dad28088284bad25c8044736b2339a341b1d89242a4489b0807c8 + languageName: node + linkType: hard + +"electron@npm:^34.0.0": + version: 34.5.8 + resolution: "electron@npm:34.5.8" + dependencies: + "@electron/get": "npm:^2.0.0" + "@types/node": "npm:^20.9.0" + extract-zip: "npm:^2.0.1" + bin: + electron: cli.js + checksum: 10c0/1fd2975d41f3ef29e165f5677137c6ce6f69ec8e7f5db9db14cfd6859c8b28ea4bc91a83cc646551918f634034ffd2cde417a5ff7631e401ca5bcb711db1aa9f + languageName: node + linkType: hard + +"end-of-stream@npm:^1.1.0": + version: 1.4.5 + resolution: "end-of-stream@npm:1.4.5" + dependencies: + once: "npm:^1.4.0" + checksum: 10c0/b0701c92a10b89afb1cb45bf54a5292c6f008d744eb4382fa559d54775ff31617d1d7bc3ef617575f552e24fad2c7c1a1835948c66b3f3a4be0a6c1f35c883d8 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-module-lexer@npm:^1.7.0": + version: 1.7.0 + resolution: "es-module-lexer@npm:1.7.0" + checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b + languageName: node + linkType: hard + +"es6-error@npm:^4.1.1": + version: 4.1.1 + resolution: "es6-error@npm:4.1.1" + checksum: 10c0/357663fb1e845c047d548c3d30f86e005db71e122678f4184ced0693f634688c3f3ef2d7de7d4af732f734de01f528b05954e270f06aa7d133679fb9fe6600ef + languageName: node + linkType: hard + +"esbuild@npm:^0.25.0, esbuild@npm:^0.25.1": + version: 0.25.12 + resolution: "esbuild@npm:0.25.12" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.12" + "@esbuild/android-arm": "npm:0.25.12" + "@esbuild/android-arm64": "npm:0.25.12" + "@esbuild/android-x64": "npm:0.25.12" + "@esbuild/darwin-arm64": "npm:0.25.12" + "@esbuild/darwin-x64": "npm:0.25.12" + "@esbuild/freebsd-arm64": "npm:0.25.12" + "@esbuild/freebsd-x64": "npm:0.25.12" + "@esbuild/linux-arm": "npm:0.25.12" + "@esbuild/linux-arm64": "npm:0.25.12" + "@esbuild/linux-ia32": "npm:0.25.12" + "@esbuild/linux-loong64": "npm:0.25.12" + "@esbuild/linux-mips64el": "npm:0.25.12" + "@esbuild/linux-ppc64": "npm:0.25.12" + "@esbuild/linux-riscv64": "npm:0.25.12" + "@esbuild/linux-s390x": "npm:0.25.12" + "@esbuild/linux-x64": "npm:0.25.12" + "@esbuild/netbsd-arm64": "npm:0.25.12" + "@esbuild/netbsd-x64": "npm:0.25.12" + "@esbuild/openbsd-arm64": "npm:0.25.12" + "@esbuild/openbsd-x64": "npm:0.25.12" + "@esbuild/openharmony-arm64": "npm:0.25.12" + "@esbuild/sunos-x64": "npm:0.25.12" + "@esbuild/win32-arm64": "npm:0.25.12" + "@esbuild/win32-ia32": "npm:0.25.12" + "@esbuild/win32-x64": "npm:0.25.12" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/c205357531423220a9de8e1e6c6514242bc9b1666e762cd67ccdf8fdfdc3f1d0bd76f8d9383958b97ad4c953efdb7b6e8c1f9ca5951cd2b7c5235e8755b34a6b + languageName: node + linkType: hard + +"esbuild@npm:^0.27.0 || ^0.28.0": + version: 0.28.1 + resolution: "esbuild@npm:0.28.1" + dependencies: + "@esbuild/aix-ppc64": "npm:0.28.1" + "@esbuild/android-arm": "npm:0.28.1" + "@esbuild/android-arm64": "npm:0.28.1" + "@esbuild/android-x64": "npm:0.28.1" + "@esbuild/darwin-arm64": "npm:0.28.1" + "@esbuild/darwin-x64": "npm:0.28.1" + "@esbuild/freebsd-arm64": "npm:0.28.1" + "@esbuild/freebsd-x64": "npm:0.28.1" + "@esbuild/linux-arm": "npm:0.28.1" + "@esbuild/linux-arm64": "npm:0.28.1" + "@esbuild/linux-ia32": "npm:0.28.1" + "@esbuild/linux-loong64": "npm:0.28.1" + "@esbuild/linux-mips64el": "npm:0.28.1" + "@esbuild/linux-ppc64": "npm:0.28.1" + "@esbuild/linux-riscv64": "npm:0.28.1" + "@esbuild/linux-s390x": "npm:0.28.1" + "@esbuild/linux-x64": "npm:0.28.1" + "@esbuild/netbsd-arm64": "npm:0.28.1" + "@esbuild/netbsd-x64": "npm:0.28.1" + "@esbuild/openbsd-arm64": "npm:0.28.1" + "@esbuild/openbsd-x64": "npm:0.28.1" + "@esbuild/openharmony-arm64": "npm:0.28.1" + "@esbuild/sunos-x64": "npm:0.28.1" + "@esbuild/win32-arm64": "npm:0.28.1" + "@esbuild/win32-ia32": "npm:0.28.1" + "@esbuild/win32-x64": "npm:0.28.1" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/29cd456a79ce35ac2c7e05fe871330416b2c395c045d849653f843e51378d6e0d6e774d6dcd01b35f4e83238a29bf8decd04fcd34b3780c589a250b21e5f92bb + languageName: node + linkType: hard + +"escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + +"expect-type@npm:^1.2.1": + version: 1.4.0 + resolution: "expect-type@npm:1.4.0" + checksum: 10c0/d40d76b8570695d36587beb3cc28494da2ca3ec8f04e67f5622ed2d372d850e401a9adef19c6835e1a8173903f157c79540b34c7b3fbd7cd8ce726cc903c57b7 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"extract-zip@npm:^2.0.1": + version: 2.0.1 + resolution: "extract-zip@npm:2.0.1" + dependencies: + "@types/yauzl": "npm:^2.9.1" + debug: "npm:^4.1.1" + get-stream: "npm:^5.1.0" + yauzl: "npm:^2.10.0" + dependenciesMeta: + "@types/yauzl": + optional: true + bin: + extract-zip: cli.js + checksum: 10c0/9afbd46854aa15a857ae0341a63a92743a7b89c8779102c3b4ffc207516b2019337353962309f85c66ee3d9092202a83cdc26dbf449a11981272038443974aee + languageName: node + linkType: hard + +"fast-glob@npm:^3.3.2": + version: 3.3.3 + resolution: "fast-glob@npm:3.3.3" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.8" + checksum: 10c0/f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.20.1 + resolution: "fastq@npm:1.20.1" + dependencies: + reusify: "npm:^1.0.4" + checksum: 10c0/e5dd725884decb1f11e5c822221d76136f239d0236f176fab80b7b8f9e7619ae57e6b4e5b73defc21e6b9ef99437ee7b545cff8e6c2c337819633712fa9d352e + languageName: node + linkType: hard + +"fd-slicer@npm:~1.1.0": + version: 1.1.0 + resolution: "fd-slicer@npm:1.1.0" + dependencies: + pend: "npm:~1.2.0" + checksum: 10c0/304dd70270298e3ffe3bcc05e6f7ade2511acc278bc52d025f8918b48b6aa3b77f10361bddfadfe2a28163f7af7adbdce96f4d22c31b2f648ba2901f0c5fc20e + languageName: node + linkType: hard + +"fdir@npm:^6.4.4, fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 + languageName: node + linkType: hard + +"fraction.js@npm:^5.3.4": + version: 5.3.4 + resolution: "fraction.js@npm:5.3.4" + checksum: 10c0/f90079fe9bfc665e0a07079938e8ff71115bce9462f17b32fc283f163b0540ec34dc33df8ed41bb56f028316b04361b9a9995b9ee9258617f8338e0b05c5f95a + languageName: node + linkType: hard + +"fs-extra@npm:^8.1.0": + version: 8.1.0 + resolution: "fs-extra@npm:8.1.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^4.0.0" + universalify: "npm:^0.1.0" + checksum: 10c0/259f7b814d9e50d686899550c4f9ded85c46c643f7fe19be69504888e007fcbc08f306fae8ec495b8b998635e997c9e3e175ff2eeed230524ef1c1684cc96423 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"get-stream@npm:^5.1.0": + version: 5.2.0 + resolution: "get-stream@npm:5.2.0" + dependencies: + pump: "npm:^3.0.0" + checksum: 10c0/43797ffd815fbb26685bf188c8cfebecb8af87b3925091dd7b9a9c915993293d78e3c9e1bce125928ff92f2d0796f3889b92b5ec6d58d1041b574682132e0a80 + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee + languageName: node + linkType: hard + +"glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: "npm:^4.0.3" + checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 + languageName: node + linkType: hard + +"global-agent@npm:^3.0.0": + version: 3.0.0 + resolution: "global-agent@npm:3.0.0" + dependencies: + boolean: "npm:^3.0.1" + es6-error: "npm:^4.1.1" + matcher: "npm:^3.0.0" + roarr: "npm:^2.15.3" + semver: "npm:^7.3.2" + serialize-error: "npm:^7.0.1" + checksum: 10c0/bb8750d026b25da437072762fd739098bad92ff72f66483c3929db4579e072f5523960f7e7fd70ee0d75db48898067b5dc1c9c1d17888128cff008fcc34d1bd3 + languageName: node + linkType: hard + +"globalthis@npm:^1.0.1": + version: 1.0.4 + resolution: "globalthis@npm:1.0.4" + dependencies: + define-properties: "npm:^1.2.1" + gopd: "npm:^1.0.1" + checksum: 10c0/9d156f313af79d80b1566b93e19285f481c591ad6d0d319b4be5e03750d004dde40a39a0f26f7e635f9007a3600802f53ecd85a759b86f109e80a5f705e01846 + languageName: node + linkType: hard + +"gopd@npm:^1.0.1": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"got@npm:^11.8.5": + version: 11.8.6 + resolution: "got@npm:11.8.6" + dependencies: + "@sindresorhus/is": "npm:^4.0.0" + "@szmarczak/http-timer": "npm:^4.0.5" + "@types/cacheable-request": "npm:^6.0.1" + "@types/responselike": "npm:^1.0.0" + cacheable-lookup: "npm:^5.0.3" + cacheable-request: "npm:^7.0.2" + decompress-response: "npm:^6.0.0" + http2-wrapper: "npm:^1.0.0-beta.5.2" + lowercase-keys: "npm:^2.0.0" + p-cancelable: "npm:^2.0.0" + responselike: "npm:^2.0.0" + checksum: 10c0/754dd44877e5cf6183f1e989ff01c648d9a4719e357457bd4c78943911168881f1cfb7b2cb15d885e2105b3ad313adb8f017a67265dd7ade771afdb261ee8cb1 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.0": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: "npm:^1.0.0" + checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 + languageName: node + linkType: hard + +"hasown@npm:^2.0.3": + version: 2.0.4 + resolution: "hasown@npm:2.0.4" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/2d8de939e270b70618f8cebb69746620db10617dbb495bc66ddad326955ea24d3ca4af133aff3eb7c1853e0218f867bc2b050ec26fe02e3aea58f880ffc5e506 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.0.0": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 + languageName: node + linkType: hard + +"http2-wrapper@npm:^1.0.0-beta.5.2": + version: 1.0.3 + resolution: "http2-wrapper@npm:1.0.3" + dependencies: + quick-lru: "npm:^5.1.1" + resolve-alpn: "npm:^1.0.0" + checksum: 10c0/6a9b72a033e9812e1476b9d776ce2f387bc94bc46c88aea0d5dab6bd47d0a539b8178830e77054dd26d1142c866d515a28a4dc7c3ff4232c88ff2ebe4f5d12d1 + languageName: node + linkType: hard + +"is-binary-path@npm:~2.1.0": + version: 2.1.0 + resolution: "is-binary-path@npm:2.1.0" + dependencies: + binary-extensions: "npm:^2.0.0" + checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 + languageName: node + linkType: hard + +"is-core-module@npm:^2.16.1": + version: 2.16.2 + resolution: "is-core-module@npm:2.16.2" + dependencies: + hasown: "npm:^2.0.3" + checksum: 10c0/14b4258390283709c15476d023ec173e27458d5d014ccdb8ed39d576e551c3fa45498b7c9fe178f1529c4cb2648ddd58852a6a62107a019f6e349529f277518a + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 + languageName: node + linkType: hard + +"isexe@npm:^4.0.0": + version: 4.0.0 + resolution: "isexe@npm:4.0.0" + checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce + languageName: node + linkType: hard + +"jiti@npm:^1.21.7": + version: 1.21.7 + resolution: "jiti@npm:1.21.7" + bin: + jiti: bin/jiti.js + checksum: 10c0/77b61989c758ff32407cdae8ddc77f85e18e1a13fc4977110dbd2e05fc761842f5f71bce684d9a01316e1c4263971315a111385759951080bbfe17cbb5de8f7a + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-tokens@npm:^9.0.1": + version: 9.0.1 + resolution: "js-tokens@npm:9.0.1" + checksum: 10c0/68dcab8f233dde211a6b5fd98079783cbcd04b53617c1250e3553ee16ab3e6134f5e65478e41d82f6d351a052a63d71024553933808570f04dbf828d7921e80e + languageName: node + linkType: hard + +"jsesc@npm:^3.0.2": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" + bin: + jsesc: bin/jsesc + checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 + languageName: node + linkType: hard + +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 + languageName: node + linkType: hard + +"json-stringify-safe@npm:^5.0.1": + version: 5.0.1 + resolution: "json-stringify-safe@npm:5.0.1" + checksum: 10c0/7dbf35cd0411d1d648dceb6d59ce5857ec939e52e4afc37601aa3da611f0987d5cee5b38d58329ceddf3ed48bd7215229c8d52059ab01f2444a338bf24ed0f37 + languageName: node + linkType: hard + +"json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"jsonfile@npm:^4.0.0": + version: 4.0.0 + resolution: "jsonfile@npm:4.0.0" + dependencies: + graceful-fs: "npm:^4.1.6" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10c0/7dc94b628d57a66b71fb1b79510d460d662eb975b5f876d723f81549c2e9cd316d58a2ddf742b2b93a4fa6b17b2accaf1a738a0e2ea114bdfb13a32e5377e480 + languageName: node + linkType: hard + +"keyv@npm:^4.0.0": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: "npm:3.0.1" + checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e + languageName: node + linkType: hard + +"lilconfig@npm:^3.1.1, lilconfig@npm:^3.1.3": + version: 3.1.3 + resolution: "lilconfig@npm:3.1.3" + checksum: 10c0/f5604e7240c5c275743561442fbc5abf2a84ad94da0f5adc71d25e31fa8483048de3dcedcb7a44112a942fed305fd75841cdf6c9681c7f640c63f1049e9a5dcc + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"loose-envify@npm:^1.1.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: "npm:^3.0.0 || ^4.0.0" + bin: + loose-envify: cli.js + checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e + languageName: node + linkType: hard + +"loupe@npm:^3.1.0, loupe@npm:^3.1.4": + version: 3.2.1 + resolution: "loupe@npm:3.2.1" + checksum: 10c0/910c872cba291309664c2d094368d31a68907b6f5913e989d301b5c25f30e97d76d77f23ab3bf3b46d0f601ff0b6af8810c10c31b91d2c6b2f132809ca2cc705 + languageName: node + linkType: hard + +"lowercase-keys@npm:^2.0.0": + version: 2.0.0 + resolution: "lowercase-keys@npm:2.0.0" + checksum: 10c0/f82a2b3568910509da4b7906362efa40f5b54ea14c2584778ddb313226f9cbf21020a5db35f9b9a0e95847a9b781d548601f31793d736b22a2b8ae8eb9ab1082 + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"magic-string@npm:^0.30.17": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + languageName: node + linkType: hard + +"matcher@npm:^3.0.0": + version: 3.0.0 + resolution: "matcher@npm:3.0.0" + dependencies: + escape-string-regexp: "npm:^4.0.0" + checksum: 10c0/2edf24194a2879690bcdb29985fc6bc0d003df44e04df21ebcac721fa6ce2f6201c579866bb92f9380bffe946f11ecd8cd31f34117fb67ebf8aca604918e127e + languageName: node + linkType: hard + +"merge2@npm:^1.3.0": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb + languageName: node + linkType: hard + +"micromatch@npm:^4.0.8": + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 + languageName: node + linkType: hard + +"mimic-response@npm:^1.0.0": + version: 1.0.1 + resolution: "mimic-response@npm:1.0.1" + checksum: 10c0/c5381a5eae997f1c3b5e90ca7f209ed58c3615caeee850e85329c598f0c000ae7bec40196580eef1781c60c709f47258131dab237cad8786f8f56750594f27fa + languageName: node + linkType: hard + +"mimic-response@npm:^3.1.0": + version: 3.1.0 + resolution: "mimic-response@npm:3.1.0" + checksum: 10c0/0d6f07ce6e03e9e4445bee655202153bdb8a98d67ee8dc965ac140900d7a2688343e6b4c9a72cfc9ef2f7944dfd76eef4ab2482eb7b293a68b84916bac735362 + languageName: node + linkType: hard + +"minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb + languageName: node + linkType: hard + +"minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"mz@npm:^2.7.0": + version: 2.7.0 + resolution: "mz@npm:2.7.0" + dependencies: + any-promise: "npm:^1.0.0" + object-assign: "npm:^4.0.1" + thenify-all: "npm:^1.0.0" + checksum: 10c0/103114e93f87362f0b56ab5b2e7245051ad0276b646e3902c98397d18bb8f4a77f2ea4a2c9d3ad516034ea3a56553b60d3f5f78220001ca4c404bd711bd0af39 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.12": + version: 3.3.15 + resolution: "nanoid@npm:3.3.15" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/e0b12e3a1d361f74150fa4b25631d0ae29f7162dab01a12f0f1be1f53b7a2a219f9b729504e474d4821207d0fe349bd3c97569ab5cf7ec2fff6aa94711956c93 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 13.0.1 + resolution: "node-gyp@npm:13.0.1" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + nopt: "npm:^10.0.0" + proc-log: "npm:^7.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.4" + tinyglobby: "npm:^0.2.12" + undici: "npm:^8.4.1" + which: "npm:^7.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/424077bc9e9bbe953a8e86db473ba818cbc6a121714008c977fd589e21e5f0c811fbf22faac730dc7182450b5e52df301811d01ae3373898658d999b7710f4e6 + languageName: node + linkType: hard + +"node-releases@npm:^2.0.50": + version: 2.0.50 + resolution: "node-releases@npm:2.0.50" + checksum: 10c0/ac75ed433864114cfd9862034960bb4f49838343dc9fc31dc7d5be8189ce5f39510ad0bb3a697efe3193d50ab6921e7a3a6ce3aae2a6f8abe62719ffd40a4cac + languageName: node + linkType: hard + +"nopt@npm:^10.0.0": + version: 10.0.1 + resolution: "nopt@npm:10.0.1" + dependencies: + abbrev: "npm:^5.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/980d89257f9587f3e1f77877ddbf905d6aa3b738ec33e49a4fa1a059a0dd82eb28063982b150654a7ae9de386f2ead60e56172db7d37cf56de545f7392a2a26a + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 + languageName: node + linkType: hard + +"normalize-url@npm:^6.0.1": + version: 6.1.0 + resolution: "normalize-url@npm:6.1.0" + checksum: 10c0/95d948f9bdd2cfde91aa786d1816ae40f8262946e13700bf6628105994fe0ff361662c20af3961161c38a119dc977adeb41fc0b41b1745eb77edaaf9cb22db23 + languageName: node + linkType: hard + +"object-assign@npm:^4.0.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"object-hash@npm:^3.0.0": + version: 3.0.0 + resolution: "object-hash@npm:3.0.0" + checksum: 10c0/a06844537107b960c1c8b96cd2ac8592a265186bfa0f6ccafe0d34eabdb526f6fa81da1f37c43df7ed13b12a4ae3457a16071603bcd39d8beddb5f08c37b0f47 + languageName: node + linkType: hard + +"object-keys@npm:^1.1.1": + version: 1.1.1 + resolution: "object-keys@npm:1.1.1" + checksum: 10c0/b11f7ccdbc6d406d1f186cdadb9d54738e347b2692a14439ca5ac70c225fa6db46db809711b78589866d47b25fc3e8dee0b4c722ac751e11180f9380e3d8601d + languageName: node + linkType: hard + +"once@npm:^1.3.1, once@npm:^1.4.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 + languageName: node + linkType: hard + +"p-cancelable@npm:^2.0.0": + version: 2.1.1 + resolution: "p-cancelable@npm:2.1.1" + checksum: 10c0/8c6dc1f8dd4154fd8b96a10e55a3a832684c4365fb9108056d89e79fbf21a2465027c04a59d0d797b5ffe10b54a61a32043af287d5c4860f1e996cbdbc847f01 + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 + languageName: node + linkType: hard + +"pathval@npm:^2.0.0": + version: 2.0.1 + resolution: "pathval@npm:2.0.1" + checksum: 10c0/460f4709479fbf2c45903a65655fc8f0a5f6d808f989173aeef5fdea4ff4f303dc13f7870303999add60ec49d4c14733895c0a869392e9866f1091fa64fd7581 + languageName: node + linkType: hard + +"pend@npm:~1.2.0": + version: 1.2.0 + resolution: "pend@npm:1.2.0" + checksum: 10c0/8a87e63f7a4afcfb0f9f77b39bb92374afc723418b9cb716ee4257689224171002e07768eeade4ecd0e86f1fa3d8f022994219fb45634f2dbd78c6803e452458 + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": + version: 2.3.2 + resolution: "picomatch@npm:2.3.2" + checksum: 10c0/a554d1709e59be97d1acb9eaedbbc700a5c03dbd4579807baed95100b00420bc729335440ef15004ae2378984e2487a7c1cebd743cfdb72b6fa9ab69223c0d61 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.2, picomatch@npm:^4.0.3, picomatch@npm:^4.0.4": + version: 4.0.5 + resolution: "picomatch@npm:4.0.5" + checksum: 10c0/947bc6b6e1ff1e6c5aaf95b107a0839d12802f4f7b867663f67d47accba939ca1cb582cf99dfc30438efa1c4648ac5990967e783e8929c36b03e8440704ef1bd + languageName: node + linkType: hard + +"pify@npm:^2.3.0": + version: 2.3.0 + resolution: "pify@npm:2.3.0" + checksum: 10c0/551ff8ab830b1052633f59cb8adc9ae8407a436e06b4a9718bcb27dc5844b83d535c3a8512b388b6062af65a98c49bdc0dd523d8b2617b188f7c8fee457158dc + languageName: node + linkType: hard + +"pirates@npm:^4.0.1": + version: 4.0.7 + resolution: "pirates@npm:4.0.7" + checksum: 10c0/a51f108dd811beb779d58a76864bbd49e239fa40c7984cd11596c75a121a8cc789f1c8971d8bb15f0dbf9d48b76c05bb62fcbce840f89b688c0fa64b37e8478a + languageName: node + linkType: hard + +"postcss-import@npm:^15.1.0": + version: 15.1.0 + resolution: "postcss-import@npm:15.1.0" + dependencies: + postcss-value-parser: "npm:^4.0.0" + read-cache: "npm:^1.0.0" + resolve: "npm:^1.1.7" + peerDependencies: + postcss: ^8.0.0 + checksum: 10c0/518aee5c83ea6940e890b0be675a2588db68b2582319f48c3b4e06535a50ea6ee45f7e63e4309f8754473245c47a0372632378d1d73d901310f295a92f26f17b + languageName: node + linkType: hard + +"postcss-js@npm:^4.0.1": + version: 4.1.0 + resolution: "postcss-js@npm:4.1.0" + dependencies: + camelcase-css: "npm:^2.0.1" + peerDependencies: + postcss: ^8.4.21 + checksum: 10c0/a3cf6e725f3e9ecd7209732f8844a0063a1380b718ccbcf93832b6ec2cd7e63ff70dd2fed49eb2483c7482296860a0f7badd3115b5d0fa05ea648eb6d9dfc9c6 + languageName: node + linkType: hard + +"postcss-load-config@npm:^4.0.2 || ^5.0 || ^6.0": + version: 6.0.1 + resolution: "postcss-load-config@npm:6.0.1" + dependencies: + lilconfig: "npm:^3.1.1" + peerDependencies: + jiti: ">=1.21.0" + postcss: ">=8.0.9" + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + checksum: 10c0/74173a58816dac84e44853f7afbd283f4ef13ca0b6baeba27701214beec33f9e309b128f8102e2b173e8d45ecba45d279a9be94b46bf48d219626aa9b5730848 + languageName: node + linkType: hard + +"postcss-nested@npm:^6.2.0": + version: 6.2.0 + resolution: "postcss-nested@npm:6.2.0" + dependencies: + postcss-selector-parser: "npm:^6.1.1" + peerDependencies: + postcss: ^8.2.14 + checksum: 10c0/7f9c3f2d764191a39364cbdcec350f26a312431a569c9ef17408021424726b0d67995ff5288405e3724bb7152a4c92f73c027e580ec91e798800ed3c52e2bc6e + languageName: node + linkType: hard + +"postcss-selector-parser@npm:^6.1.1, postcss-selector-parser@npm:^6.1.2": + version: 6.1.4 + resolution: "postcss-selector-parser@npm:6.1.4" + dependencies: + cssesc: "npm:^3.0.0" + util-deprecate: "npm:^1.0.2" + checksum: 10c0/996f3290dee08ecb073d5f396d1134e619494cfd83140aec07a29618ee1e76d370769a8959d4c0cfefb24aa96dcffa4e7c4937dd881e03b735d50cba959ceb19 + languageName: node + linkType: hard + +"postcss-value-parser@npm:^4.0.0, postcss-value-parser@npm:^4.2.0": + version: 4.2.0 + resolution: "postcss-value-parser@npm:4.2.0" + checksum: 10c0/f4142a4f56565f77c1831168e04e3effd9ffcc5aebaf0f538eee4b2d465adfd4b85a44257bb48418202a63806a7da7fe9f56c330aebb3cac898e46b4cbf49161 + languageName: node + linkType: hard + +"postcss@npm:^8.4.47, postcss@npm:^8.5.1, postcss@npm:^8.5.3, postcss@npm:^8.5.6": + version: 8.5.16 + resolution: "postcss@npm:8.5.16" + dependencies: + nanoid: "npm:^3.3.12" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/625de7a02f662f3a340964d14b487bd5097adf16f5f171e257d19005ba37aea8768ee446557500e88e91ca46b4d14d6cb4a0bf033c6ec0c8c0b660d85719f1ef + languageName: node + linkType: hard + +"proc-log@npm:^7.0.0": + version: 7.0.0 + resolution: "proc-log@npm:7.0.0" + checksum: 10c0/b89c2d862604f35fec795477b0c7e376feab3ba0d4f4d291c4e959567442697cf451ac557d0623c1cc38af45a78128b983410f397a10c5d3a67f76c33de4754b + languageName: node + linkType: hard + +"progress@npm:^2.0.3": + version: 2.0.3 + resolution: "progress@npm:2.0.3" + checksum: 10c0/1697e07cb1068055dbe9fe858d242368ff5d2073639e652b75a7eb1f2a1a8d4afd404d719de23c7b48481a6aa0040686310e2dac2f53d776daa2176d3f96369c + languageName: node + linkType: hard + +"pump@npm:^3.0.0": + version: 3.0.4 + resolution: "pump@npm:3.0.4" + dependencies: + end-of-stream: "npm:^1.1.0" + once: "npm:^1.3.1" + checksum: 10c0/2780e66b5471c19e3e3e1063b84f3f6a3a08367f24c5ed552f98cd5901e6ada27c7ad6495d4244f553fd03b01884a4561933064f053f47c8994d84fd352768ea + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 + languageName: node + linkType: hard + +"quick-lru@npm:^5.1.1": + version: 5.1.1 + resolution: "quick-lru@npm:5.1.1" + checksum: 10c0/a24cba5da8cec30d70d2484be37622580f64765fb6390a928b17f60cd69e8dbd32a954b3ff9176fa1b86d86ff2ba05252fae55dc4d40d0291c60412b0ad096da + languageName: node + linkType: hard + +"react-dom@npm:^18.3.1": + version: 18.3.1 + resolution: "react-dom@npm:18.3.1" + dependencies: + loose-envify: "npm:^1.1.0" + scheduler: "npm:^0.23.2" + peerDependencies: + react: ^18.3.1 + checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85 + languageName: node + linkType: hard + +"react-refresh@npm:^0.17.0": + version: 0.17.0 + resolution: "react-refresh@npm:0.17.0" + checksum: 10c0/002cba940384c9930008c0bce26cac97a9d5682bc623112c2268ba0c155127d9c178a9a5cc2212d560088d60dfd503edd808669a25f9b377f316a32361d0b23c + languageName: node + linkType: hard + +"react@npm:^18.3.1": + version: 18.3.1 + resolution: "react@npm:18.3.1" + dependencies: + loose-envify: "npm:^1.1.0" + checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3 + languageName: node + linkType: hard + +"read-cache@npm:^1.0.0": + version: 1.0.0 + resolution: "read-cache@npm:1.0.0" + dependencies: + pify: "npm:^2.3.0" + checksum: 10c0/90cb2750213c7dd7c80cb420654344a311fdec12944e81eb912cd82f1bc92aea21885fa6ce442e3336d9fccd663b8a7a19c46d9698e6ca55620848ab932da814 + languageName: node + linkType: hard + +"readdirp@npm:~3.6.0": + version: 3.6.0 + resolution: "readdirp@npm:3.6.0" + dependencies: + picomatch: "npm:^2.2.1" + checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b + languageName: node + linkType: hard + +"resolve-alpn@npm:^1.0.0": + version: 1.2.1 + resolution: "resolve-alpn@npm:1.2.1" + checksum: 10c0/b70b29c1843bc39781ef946c8cd4482e6d425976599c0f9c138cec8209e4e0736161bf39319b01676a847000085dfdaf63583c6fb4427bf751a10635bd2aa0c4 + languageName: node + linkType: hard + +"resolve@npm:^1.1.7, resolve@npm:^1.22.8": + version: 1.22.12 + resolution: "resolve@npm:1.22.12" + dependencies: + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/b16dc9b537c02e8c3388f7d3dcff9741d3071625f9a97ac1c885f2b0ca51e78df22328fb6d6ef214dd9101fb7cfc19aa2836fe3410402a94f3f7b8639c7149bf + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.1.7#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": + version: 1.22.12 + resolution: "resolve@patch:resolve@npm%3A1.22.12#optional!builtin::version=1.22.12&hash=c3c19d" + dependencies: + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/fc6519984ae1f894d877c0060ba8b1f5ba3bc0e85a02f74e141929c118c23d74d9735619a9cc2965397387e514884245c65d72a40731dcb6cfc84c7bcdc8321e + languageName: node + linkType: hard + +"responselike@npm:^2.0.0": + version: 2.0.1 + resolution: "responselike@npm:2.0.1" + dependencies: + lowercase-keys: "npm:^2.0.0" + checksum: 10c0/360b6deb5f101a9f8a4174f7837c523c3ec78b7ca8a7c1d45a1062b303659308a23757e318b1e91ed8684ad1205721142dd664d94771cd63499353fd4ee732b5 + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.1.0 + resolution: "reusify@npm:1.1.0" + checksum: 10c0/4eff0d4a5f9383566c7d7ec437b671cc51b25963bd61bf127c3f3d3f68e44a026d99b8d2f1ad344afff8d278a8fe70a8ea092650a716d22287e8bef7126bb2fa + languageName: node + linkType: hard + +"roarr@npm:^2.15.3": + version: 2.15.4 + resolution: "roarr@npm:2.15.4" + dependencies: + boolean: "npm:^3.0.1" + detect-node: "npm:^2.0.4" + globalthis: "npm:^1.0.1" + json-stringify-safe: "npm:^5.0.1" + semver-compare: "npm:^1.0.0" + sprintf-js: "npm:^1.1.2" + checksum: 10c0/7d01d4c14513c461778dd673a8f9e53255221f8d04173aafeb8e11b23d8b659bb83f1c90cfe81af7f9c213b8084b404b918108fd792bda76678f555340cc64ec + languageName: node + linkType: hard + +"rollup@npm:^4.34.9, rollup@npm:^4.43.0": + version: 4.62.2 + resolution: "rollup@npm:4.62.2" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.62.2" + "@rollup/rollup-android-arm64": "npm:4.62.2" + "@rollup/rollup-darwin-arm64": "npm:4.62.2" + "@rollup/rollup-darwin-x64": "npm:4.62.2" + "@rollup/rollup-freebsd-arm64": "npm:4.62.2" + "@rollup/rollup-freebsd-x64": "npm:4.62.2" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.62.2" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.62.2" + "@rollup/rollup-linux-arm64-gnu": "npm:4.62.2" + "@rollup/rollup-linux-arm64-musl": "npm:4.62.2" + "@rollup/rollup-linux-loong64-gnu": "npm:4.62.2" + "@rollup/rollup-linux-loong64-musl": "npm:4.62.2" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.62.2" + "@rollup/rollup-linux-ppc64-musl": "npm:4.62.2" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.62.2" + "@rollup/rollup-linux-riscv64-musl": "npm:4.62.2" + "@rollup/rollup-linux-s390x-gnu": "npm:4.62.2" + "@rollup/rollup-linux-x64-gnu": "npm:4.62.2" + "@rollup/rollup-linux-x64-musl": "npm:4.62.2" + "@rollup/rollup-openbsd-x64": "npm:4.62.2" + "@rollup/rollup-openharmony-arm64": "npm:4.62.2" + "@rollup/rollup-win32-arm64-msvc": "npm:4.62.2" + "@rollup/rollup-win32-ia32-msvc": "npm:4.62.2" + "@rollup/rollup-win32-x64-gnu": "npm:4.62.2" + "@rollup/rollup-win32-x64-msvc": "npm:4.62.2" + "@types/estree": "npm:1.0.9" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-loong64-musl": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openbsd-x64": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/83ff5f4a1fea3fa05db2ef56beceee8c33d4a72b818e19c562f1e85c41076fe5b12aadc44048bb73e60b83336df82154b017fa6bf0186f3141643e6f215fbdcb + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 + languageName: node + linkType: hard + +"scheduler@npm:^0.23.2": + version: 0.23.2 + resolution: "scheduler@npm:0.23.2" + dependencies: + loose-envify: "npm:^1.1.0" + checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78 + languageName: node + linkType: hard + +"semver-compare@npm:^1.0.0": + version: 1.0.0 + resolution: "semver-compare@npm:1.0.0" + checksum: 10c0/9ef4d8b81847556f0865f46ddc4d276bace118c7cb46811867af82e837b7fc473911981d5a0abc561fa2db487065572217e5b06e18701c4281bcdd2a1affaff1 + languageName: node + linkType: hard + +"semver@npm:^6.2.0, semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.3.2, semver@npm:^7.3.5": + version: 7.8.5 + resolution: "semver@npm:7.8.5" + bin: + semver: bin/semver.js + checksum: 10c0/b1f3127a5be8125a94f37188b361c212466c292c6910adce3ec106cff5dc211ccaedc4739c11bb70fda59d6fc1f040a9bca289f4e093451521a2372e5231fe0c + languageName: node + linkType: hard + +"serialize-error@npm:^7.0.1": + version: 7.0.1 + resolution: "serialize-error@npm:7.0.1" + dependencies: + type-fest: "npm:^0.13.1" + checksum: 10c0/7982937d578cd901276c8ab3e2c6ed8a4c174137730f1fb0402d005af209a0e84d04acc874e317c936724c7b5b26c7a96ff7e4b8d11a469f4924a4b0ea814c05 + languageName: node + linkType: hard + +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"sprintf-js@npm:^1.1.2": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec + languageName: node + linkType: hard + +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 + languageName: node + linkType: hard + +"std-env@npm:^3.9.0": + version: 3.10.0 + resolution: "std-env@npm:3.10.0" + checksum: 10c0/1814927a45004d36dde6707eaf17552a546769bc79a6421be2c16ce77d238158dfe5de30910b78ec30d95135cc1c59ea73ee22d2ca170f8b9753f84da34c427f + languageName: node + linkType: hard + +"strip-literal@npm:^3.0.0": + version: 3.1.0 + resolution: "strip-literal@npm:3.1.0" + dependencies: + js-tokens: "npm:^9.0.1" + checksum: 10c0/50918f669915d9ad0fe4b7599902b735f853f2201c97791ead00104a654259c0c61bc2bc8fa3db05109339b61f4cf09e47b94ecc874ffbd0e013965223893af8 + languageName: node + linkType: hard + +"sucrase@npm:^3.35.0": + version: 3.35.1 + resolution: "sucrase@npm:3.35.1" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.2" + commander: "npm:^4.0.0" + lines-and-columns: "npm:^1.1.6" + mz: "npm:^2.7.0" + pirates: "npm:^4.0.1" + tinyglobby: "npm:^0.2.11" + ts-interface-checker: "npm:^0.1.9" + bin: + sucrase: bin/sucrase + sucrase-node: bin/sucrase-node + checksum: 10c0/6fa22329c261371feb9560630d961ad0d0b9c87dce21ea74557c5f3ffbe5c1ee970ea8bcce9962ae9c90c3c47165ffa7dd41865c7414f5d8ea7a40755d612c5c + languageName: node + linkType: hard + +"sumchecker@npm:^3.0.1": + version: 3.0.1 + resolution: "sumchecker@npm:3.0.1" + dependencies: + debug: "npm:^4.1.0" + checksum: 10c0/43c387be9dfe22dbeaf39dfa4ffb279847aeb37a42a8988c0b066f548bbd209aa8c65e03da29f2b29be1a66b577801bf89fff0007df4183db2f286263a9569e5 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"tailwindcss@npm:^3.4.17": + version: 3.4.19 + resolution: "tailwindcss@npm:3.4.19" + dependencies: + "@alloc/quick-lru": "npm:^5.2.0" + arg: "npm:^5.0.2" + chokidar: "npm:^3.6.0" + didyoumean: "npm:^1.2.2" + dlv: "npm:^1.1.3" + fast-glob: "npm:^3.3.2" + glob-parent: "npm:^6.0.2" + is-glob: "npm:^4.0.3" + jiti: "npm:^1.21.7" + lilconfig: "npm:^3.1.3" + micromatch: "npm:^4.0.8" + normalize-path: "npm:^3.0.0" + object-hash: "npm:^3.0.0" + picocolors: "npm:^1.1.1" + postcss: "npm:^8.4.47" + postcss-import: "npm:^15.1.0" + postcss-js: "npm:^4.0.1" + postcss-load-config: "npm:^4.0.2 || ^5.0 || ^6.0" + postcss-nested: "npm:^6.2.0" + postcss-selector-parser: "npm:^6.1.2" + resolve: "npm:^1.22.8" + sucrase: "npm:^3.35.0" + bin: + tailwind: lib/cli.js + tailwindcss: lib/cli.js + checksum: 10c0/e1063daccb9e5a508b357ec73b0011354204b2366b56496d6f0cc822733a55a0551502cb85856a2257ef9b676d0026616daaaa176d391f3216df57fbd693c581 + languageName: node + linkType: hard + +"tar@npm:^7.5.4": + version: 7.5.19 + resolution: "tar@npm:7.5.19" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/7022e8cb04a8ceccc0689f2c731743fa2aab2e3c3f559f7dbc37b65ef7d5913049b427284eded2ec0765c5db5ff72dd7939fe2ae15785ff422cef2116c95d798 + languageName: node + linkType: hard + +"thenify-all@npm:^1.0.0": + version: 1.6.0 + resolution: "thenify-all@npm:1.6.0" + dependencies: + thenify: "npm:>= 3.1.0 < 4" + checksum: 10c0/9b896a22735e8122754fe70f1d65f7ee691c1d70b1f116fda04fea103d0f9b356e3676cb789506e3909ae0486a79a476e4914b0f92472c2e093d206aed4b7d6b + languageName: node + linkType: hard + +"thenify@npm:>= 3.1.0 < 4": + version: 3.3.1 + resolution: "thenify@npm:3.3.1" + dependencies: + any-promise: "npm:^1.0.0" + checksum: 10c0/f375aeb2b05c100a456a30bc3ed07ef03a39cbdefe02e0403fb714b8c7e57eeaad1a2f5c4ecfb9ce554ce3db9c2b024eba144843cd9e344566d9fcee73b04767 + languageName: node + linkType: hard + +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + +"tinyexec@npm:^0.3.2": + version: 0.3.2 + resolution: "tinyexec@npm:0.3.2" + checksum: 10c0/3efbf791a911be0bf0821eab37a3445c2ba07acc1522b1fa84ae1e55f10425076f1290f680286345ed919549ad67527d07281f1c19d584df3b74326909eb1f90 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.11, tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.15": + version: 0.2.17 + resolution: "tinyglobby@npm:0.2.17" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10c0/7f7bb0f197c88bc4b20c231e0deca4240ca3bf313a88f5a7fee93a872b84966a4d50220947c0455ad07a60b3b360961c5b7fd979222aeb716a9f99b412002e4c + languageName: node + linkType: hard + +"tinypool@npm:^1.1.1": + version: 1.1.1 + resolution: "tinypool@npm:1.1.1" + checksum: 10c0/bf26727d01443061b04fa863f571016950888ea994ba0cd8cba3a1c51e2458d84574341ab8dbc3664f1c3ab20885c8cf9ff1cc4b18201f04c2cde7d317fff69b + languageName: node + linkType: hard + +"tinyrainbow@npm:^2.0.0": + version: 2.0.0 + resolution: "tinyrainbow@npm:2.0.0" + checksum: 10c0/c83c52bef4e0ae7fb8ec6a722f70b5b6fa8d8be1c85792e829f56c0e1be94ab70b293c032dc5048d4d37cfe678f1f5babb04bdc65fd123098800148ca989184f + languageName: node + linkType: hard + +"tinyspy@npm:^4.0.3": + version: 4.0.4 + resolution: "tinyspy@npm:4.0.4" + checksum: 10c0/a8020fc17799251e06a8398dcc352601d2770aa91c556b9531ecd7a12581161fd1c14e81cbdaff0c1306c93bfdde8ff6d1c1a3f9bbe6d91604f0fd4e01e2f1eb + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + languageName: node + linkType: hard + +"ts-interface-checker@npm:^0.1.9": + version: 0.1.13 + resolution: "ts-interface-checker@npm:0.1.13" + checksum: 10c0/232509f1b84192d07b81d1e9b9677088e590ac1303436da1e92b296e9be8e31ea042e3e1fd3d29b1742ad2c959e95afe30f63117b8f1bc3a3850070a5142fea7 + languageName: node + linkType: hard + +"type-fest@npm:^0.13.1": + version: 0.13.1 + resolution: "type-fest@npm:0.13.1" + checksum: 10c0/0c0fa07ae53d4e776cf4dac30d25ad799443e9eef9226f9fddbb69242db86b08584084a99885cfa5a9dfe4c063ebdc9aa7b69da348e735baede8d43f1aeae93b + languageName: node + linkType: hard + +"typescript@npm:^5.7.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.7.3#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=8c6c40" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6f7e53bf0d9702350deeb6f35e08b69cbc8b958c33e0ec77bdc0ad6a6c8e280f3959dcbfde6f5b0848bece57810696489deaaa53d75de3578ff255d168c1efbd + languageName: node + linkType: hard + +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04 + languageName: node + linkType: hard + +"undici-types@npm:~8.3.0": + version: 8.3.0 + resolution: "undici-types@npm:8.3.0" + checksum: 10c0/c8aa7e2fbebfce519654dafadc0ece59be888d2ccaf180fb4495da875e7b536d2456345c384069c7e6f3e9c9ab7435f074957da306f142343eee86ff8048855a + languageName: node + linkType: hard + +"undici@npm:^8.4.1": + version: 8.7.0 + resolution: "undici@npm:8.7.0" + checksum: 10c0/b7e5ecb4de82fa4f905011a77544fe7ba4da06f27167ff99313a7ae2869f3cb233d676dcd085533bc69f36989bc40871f5ae6ea6e4c9b91db92616b9e91b579d + languageName: node + linkType: hard + +"universalify@npm:^0.1.0": + version: 0.1.2 + resolution: "universalify@npm:0.1.2" + checksum: 10c0/e70e0339f6b36f34c9816f6bf9662372bd241714dc77508d231d08386d94f2c4aa1ba1318614f92015f40d45aae1b9075cd30bd490efbe39387b60a76ca3f045 + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.2.3": + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec + languageName: node + linkType: hard + +"util-deprecate@npm:^1.0.2": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 + languageName: node + linkType: hard + +"vite-node@npm:3.2.4": + version: 3.2.4 + resolution: "vite-node@npm:3.2.4" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.4.1" + es-module-lexer: "npm:^1.7.0" + pathe: "npm:^2.0.3" + vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0" + bin: + vite-node: vite-node.mjs + checksum: 10c0/6ceca67c002f8ef6397d58b9539f80f2b5d79e103a18367288b3f00a8ab55affa3d711d86d9112fce5a7fa658a212a087a005a045eb8f4758947dd99af2a6c6b + languageName: node + linkType: hard + +"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0": + version: 7.3.6 + resolution: "vite@npm:7.3.6" + dependencies: + esbuild: "npm:^0.27.0 || ^0.28.0" + fdir: "npm:^6.5.0" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.3" + postcss: "npm:^8.5.6" + rollup: "npm:^4.43.0" + tinyglobby: "npm:^0.2.15" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/c6d359f84ad362f5c97ff7988b77c90add04162c60cdf6059375a7d9e3217848c53a8cb6b6c48517bef84b6bba4c9bc85319a889b5236acb7d5a2bc8cb7b9a8d + languageName: node + linkType: hard + +"vite@npm:^6.1.0": + version: 6.4.3 + resolution: "vite@npm:6.4.3" + dependencies: + esbuild: "npm:^0.25.0" + fdir: "npm:^6.4.4" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.2" + postcss: "npm:^8.5.3" + rollup: "npm:^4.34.9" + tinyglobby: "npm:^0.2.13" + peerDependencies: + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: ">=1.21.0" + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/23ce22e50d5c7a87321f04b155c5bc3cf915e34e6c40918975741ed5e8b0c257039a643f1bc1cd829ee814ad92a138704e82084b7ebe40b399a62d8effea630c + languageName: node + linkType: hard + +"vitest@npm:^3.0.5": + version: 3.2.7 + resolution: "vitest@npm:3.2.7" + dependencies: + "@types/chai": "npm:^5.2.2" + "@vitest/expect": "npm:3.2.7" + "@vitest/mocker": "npm:3.2.7" + "@vitest/pretty-format": "npm:^3.2.7" + "@vitest/runner": "npm:3.2.7" + "@vitest/snapshot": "npm:3.2.7" + "@vitest/spy": "npm:3.2.7" + "@vitest/utils": "npm:3.2.7" + chai: "npm:^5.2.0" + debug: "npm:^4.4.1" + expect-type: "npm:^1.2.1" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.2" + std-env: "npm:^3.9.0" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^0.3.2" + tinyglobby: "npm:^0.2.14" + tinypool: "npm:^1.1.1" + tinyrainbow: "npm:^2.0.0" + vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0" + vite-node: "npm:3.2.4" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.2.7 + "@vitest/ui": 3.2.7 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/debug": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: ./vitest.mjs + checksum: 10c0/4eb7a63a1d62b88c425bcbc609835055a5644a1994d6f47605fe396dddf732e3c0277c9ec89a028fe81557dc4051dd15fc15b9eba6a51d1466cd5c682ddc1261 + languageName: node + linkType: hard + +"which@npm:^7.0.0": + version: 7.0.0 + resolution: "which@npm:7.0.0" + dependencies: + isexe: "npm:^4.0.0" + bin: + node-which: bin/which.js + checksum: 10c0/ca0b54f198f78bbc4b7c02e34bda8d335cb352e0adb4cbca1c37b1a957af3a879a82c4c27ca6525bc942f548d8b64f816ef6528360af9f3de55ffb9b979b620d + languageName: node + linkType: hard + +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"yauzl@npm:^2.10.0": + version: 2.10.0 + resolution: "yauzl@npm:2.10.0" + dependencies: + buffer-crc32: "npm:~0.2.3" + fd-slicer: "npm:~1.1.0" + checksum: 10c0/f265002af7541b9ec3589a27f5fb8f11cf348b53cc15e2751272e3c062cd73f3e715bc72d43257de71bbaecae446c3f1b14af7559e8ab0261625375541816422 + languageName: node + linkType: hard