summaryrefslogtreecommitdiff
path: root/makima/frontend
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-05-02 15:07:33 +0100
committerGitHub <noreply@github.com>2026-05-02 15:07:33 +0100
commit760516b2e7b97fa389fb3902e8d2314eea052ff0 (patch)
tree69eb1dd212ef924ee9e451d8d88806f899c03e84 /makima/frontend
parente11759447b1ac00becfb1e979e488f7f9c9cf478 (diff)
downloadsoryu-760516b2e7b97fa389fb3902e8d2314eea052ff0.tar.gz
soryu-760516b2e7b97fa389fb3902e8d2314eea052ff0.zip
feat: multi-document directives with ephemeral task lifecycle (#119)
* feat: soryu-co/soryu - makima: Fix folder/file naming and breadcrumb hash bugs * WIP: heartbeat checkpoint * WIP: heartbeat checkpoint * WIP: heartbeat checkpoint * feat: soryu-co/soryu - makima: Frontend: render multiple documents per directive folder * WIP: heartbeat checkpoint * WIP: heartbeat checkpoint * WIP: heartbeat checkpoint * Fix DirectiveRevision import in openapi.rs after merge * Fix document-directives.tsx merge artifacts and add inactive status
Diffstat (limited to 'makima/frontend')
-rw-r--r--makima/frontend/pnpm-lock.yaml1913
-rw-r--r--makima/frontend/src/lib/api.ts114
-rw-r--r--makima/frontend/src/routes/document-directives.tsx2352
-rw-r--r--makima/frontend/tsconfig.tsbuildinfo2
4 files changed, 2592 insertions, 1789 deletions
diff --git a/makima/frontend/pnpm-lock.yaml b/makima/frontend/pnpm-lock.yaml
index 2dfbf67..d19a0cf 100644
--- a/makima/frontend/pnpm-lock.yaml
+++ b/makima/frontend/pnpm-lock.yaml
@@ -48,34 +48,78 @@ dependencies:
specifier: ^3.6.0
version: 3.8.1(@types/react@19.2.14)(react-dom@19.2.5)(react-is@19.2.5)(react@19.2.5)(redux@5.0.1)
-devDependencies:
- '@react-router/dev':
- specifier: ^7.1.0
- version: 7.14.2(react-router@7.14.2)(typescript@5.9.3)(vite@6.4.2)
- '@react-router/fs-routes':
- specifier: ^7.1.0
- version: 7.14.2(@react-router/dev@7.14.2)(typescript@5.9.3)
- '@tailwindcss/vite':
- specifier: ^4.0.0
- version: 4.2.4(vite@6.4.2)
- '@types/react':
- specifier: ^19.0.1
- version: 19.2.14
- '@types/react-dom':
- specifier: ^19.0.1
- version: 19.2.3(@types/react@19.2.14)
- '@vitejs/plugin-react':
- specifier: ^4.3.4
- version: 4.7.0(vite@6.4.2)
- tailwindcss:
- specifier: ^4.0.0
- version: 4.2.4
- typescript:
- specifier: ^5.7.2
- version: 5.9.3
- vite:
- specifier: ^6.0.5
- version: 6.4.2
+ .:
+ dependencies:
+ '@lexical/history':
+ specifier: ^0.44.0
+ version: 0.44.0
+ '@lexical/list':
+ specifier: ^0.44.0
+ version: 0.44.0
+ '@lexical/markdown':
+ specifier: ^0.44.0
+ version: 0.44.0
+ '@lexical/react':
+ specifier: ^0.44.0
+ version: 0.44.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(yjs@13.6.30)
+ '@lexical/rich-text':
+ specifier: ^0.44.0
+ version: 0.44.0
+ '@lexical/selection':
+ specifier: ^0.44.0
+ version: 0.44.0
+ '@lexical/utils':
+ specifier: ^0.44.0
+ version: 0.44.0
+ '@supabase/supabase-js':
+ specifier: ^2.90.1
+ version: 2.95.2
+ '@xyflow/react':
+ specifier: ^12.10.0
+ version: 12.10.0(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ lexical:
+ specifier: ^0.44.0
+ version: 0.44.0
+ react:
+ specifier: ^19.0.0
+ version: 19.2.4
+ react-dom:
+ specifier: ^19.0.0
+ version: 19.2.4(react@19.2.4)
+ react-router:
+ specifier: ^7.1.0
+ version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ recharts:
+ specifier: ^3.6.0
+ version: 3.7.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react-is@19.2.4)(react@19.2.4)(redux@5.0.1)
+ devDependencies:
+ '@react-router/dev':
+ specifier: ^7.1.0
+ version: 7.13.0(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2))
+ '@react-router/fs-routes':
+ specifier: ^7.1.0
+ version: 7.13.0(@react-router/dev@7.13.0(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)))(typescript@5.9.3)
+ '@tailwindcss/vite':
+ specifier: ^4.0.0
+ version: 4.1.18(vite@6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2))
+ '@types/react':
+ specifier: ^19.0.1
+ version: 19.2.13
+ '@types/react-dom':
+ specifier: ^19.0.1
+ version: 19.2.3(@types/react@19.2.13)
+ '@vitejs/plugin-react':
+ specifier: ^4.3.4
+ version: 4.7.0(vite@6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2))
+ tailwindcss:
+ specifier: ^4.0.0
+ version: 4.1.18
+ typescript:
+ specifier: ^5.7.2
+ version: 5.9.3
+ vite:
+ specifier: ^6.0.5
+ version: 6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)
packages:
@@ -620,48 +664,49 @@ packages:
dev: true
optional: true
- /@floating-ui/core@1.7.5:
+ '@floating-ui/core@1.7.5':
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
- dependencies:
- '@floating-ui/utils': 0.2.11
- dev: false
- /@floating-ui/dom@1.7.6:
+ '@floating-ui/dom@1.7.6':
resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
- dependencies:
- '@floating-ui/core': 1.7.5
- '@floating-ui/utils': 0.2.11
- dev: false
- /@floating-ui/react-dom@2.1.8(react-dom@19.2.5)(react@19.2.5):
+ '@floating-ui/react-dom@2.1.8':
resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
- dependencies:
- '@floating-ui/dom': 1.7.6
- react: 19.2.5
- react-dom: 19.2.5(react@19.2.5)
- dev: false
- /@floating-ui/react@0.27.19(react-dom@19.2.5)(react@19.2.5):
+ '@floating-ui/react@0.27.19':
resolution: {integrity: sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==}
peerDependencies:
react: '>=17.0.0'
react-dom: '>=17.0.0'
- dependencies:
- '@floating-ui/react-dom': 2.1.8(react-dom@19.2.5)(react@19.2.5)
- '@floating-ui/utils': 0.2.11
- react: 19.2.5
- react-dom: 19.2.5(react@19.2.5)
- tabbable: 6.4.0
- dev: false
- /@floating-ui/utils@0.2.11:
+ '@floating-ui/utils@0.2.11':
resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
- dev: false
- /@jridgewell/gen-mapping@0.3.13:
+ '@floating-ui/core@1.7.5':
+ resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
+
+ '@floating-ui/dom@1.7.6':
+ resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
+
+ '@floating-ui/react-dom@2.1.8':
+ resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/react@0.27.19':
+ resolution: {integrity: sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==}
+ peerDependencies:
+ react: '>=17.0.0'
+ react-dom: '>=17.0.0'
+
+ '@floating-ui/utils@0.2.11':
+ resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
+
+ '@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -691,133 +736,52 @@ packages:
'@jridgewell/sourcemap-codec': 1.5.5
dev: true
- /@lexical/clipboard@0.44.0:
+ '@lexical/clipboard@0.44.0':
resolution: {integrity: sha512-nfmNIs7uENqlDI7cm2E4I1Yp8mDJGMhEQIrIV2rNWnL1oeHVXQ7yuYdyoPdcY1zuj/9nvkYBQYUEh0QiGwpETA==}
- dependencies:
- '@lexical/extension': 0.44.0
- '@lexical/html': 0.44.0
- '@lexical/list': 0.44.0
- '@lexical/selection': 0.44.0
- '@lexical/utils': 0.44.0
- '@types/trusted-types': 2.0.7
- lexical: 0.44.0
- dev: false
- /@lexical/code-core@0.44.0:
+ '@lexical/code-core@0.44.0':
resolution: {integrity: sha512-m57JyXTIvW1tsqw/Vuogk8jqWCZZIeFQbWybRc46ytR8ReDgzPRODpN8+dacIIeRH5yC5UC3lAa743mtdNkxqg==}
- dependencies:
- '@lexical/extension': 0.44.0
- lexical: 0.44.0
- dev: false
- /@lexical/devtools-core@0.44.0(react-dom@19.2.5)(react@19.2.5):
+ '@lexical/devtools-core@0.44.0':
resolution: {integrity: sha512-X3uNG3P1vOsdzmEcy+7m9DxAcIVtVUZnvskmLqqLs6VluVVwH9xy7h1bPsvlDKvj1Nj73tWJ3TW0qXQWDTo5tw==}
peerDependencies:
react: '>=17.x'
react-dom: '>=17.x'
- dependencies:
- '@lexical/html': 0.44.0
- '@lexical/link': 0.44.0
- '@lexical/mark': 0.44.0
- '@lexical/table': 0.44.0
- '@lexical/utils': 0.44.0
- lexical: 0.44.0
- react: 19.2.5
- react-dom: 19.2.5(react@19.2.5)
- dev: false
- /@lexical/dragon@0.44.0:
+ '@lexical/dragon@0.44.0':
resolution: {integrity: sha512-RhlsjVDket9k1+YFEkDE0/7Qyrh2BI0vxBMzrWwPJTXX/4YFanYN9su8RSabkIukBBJ3QiNOOoC8FKK4Lkr4qg==}
- dependencies:
- '@lexical/extension': 0.44.0
- lexical: 0.44.0
- dev: false
- /@lexical/extension@0.44.0:
+ '@lexical/extension@0.44.0':
resolution: {integrity: sha512-BsYtoc+0EU0pqcOpf/lIUDU6LQVO6zX2AawZoUWJzT3Wzfov23qsqZWvl2WGM9dnRTN5iISJL3Fl53bQVxiXxw==}
- dependencies:
- '@lexical/utils': 0.44.0
- '@preact/signals-core': 1.14.1
- lexical: 0.44.0
- dev: false
- /@lexical/hashtag@0.44.0:
+ '@lexical/hashtag@0.44.0':
resolution: {integrity: sha512-0WATahDSqYKVTudQv3KpFbLeCpmrCpRptPFbjxOMckAX2MRpYlrExlqKfgfpri5BSQPtG49EPSGeNfSx/Faavw==}
- dependencies:
- '@lexical/text': 0.44.0
- '@lexical/utils': 0.44.0
- lexical: 0.44.0
- dev: false
- /@lexical/history@0.44.0:
+ '@lexical/history@0.44.0':
resolution: {integrity: sha512-RGXcbFTgYL1GIWaReBI26mNSsJTfiA9EAtDY4LBeZ14NrIQhYNokKgNiOxq5Bn8xXrl2+mawQEqoMfgpWp/5YA==}
- dependencies:
- '@lexical/extension': 0.44.0
- '@lexical/utils': 0.44.0
- lexical: 0.44.0
- dev: false
- /@lexical/html@0.44.0:
+ '@lexical/html@0.44.0':
resolution: {integrity: sha512-5X6eGsgwtqPxABsuShUxF7ZfyB/U4GwSEyeonvwH1Vc/5Q2uQVjlB+FAYd+MNwWMHMh4d4+yZ3l70AtIuhr5eg==}
- dependencies:
- '@lexical/extension': 0.44.0
- '@lexical/selection': 0.44.0
- '@lexical/utils': 0.44.0
- lexical: 0.44.0
- dev: false
- /@lexical/link@0.44.0:
+ '@lexical/link@0.44.0':
resolution: {integrity: sha512-uvEqEol/mLEzGVQd8Rok9I48RgYPKokM/nsclI9nYcEdccVOM2Nri4ntoRwodhbccFLtjMPl8OBldwXbfc77tQ==}
- dependencies:
- '@lexical/extension': 0.44.0
- '@lexical/utils': 0.44.0
- lexical: 0.44.0
- dev: false
- /@lexical/list@0.44.0:
+ '@lexical/list@0.44.0':
resolution: {integrity: sha512-ZTCWxDz1okPrC9FBXi1yV3W5fbQQeMUlFIcSVF9HibcVPmCsPa900IxthuiQbGiTycUyXDTOB3IUYRtlJNtpjw==}
- dependencies:
- '@lexical/extension': 0.44.0
- '@lexical/utils': 0.44.0
- lexical: 0.44.0
- dev: false
- /@lexical/mark@0.44.0:
+ '@lexical/mark@0.44.0':
resolution: {integrity: sha512-bWMowllwe6BcgYMAkrsZx6Z+CX/72qCQpFKhlkR4ael92yOWSBkz68xp1wxxkSnQX9zoI1gYTeWBofVsSDKcsQ==}
- dependencies:
- '@lexical/utils': 0.44.0
- lexical: 0.44.0
- dev: false
- /@lexical/markdown@0.44.0:
+ '@lexical/markdown@0.44.0':
resolution: {integrity: sha512-DwlXdp85pYMo3exDF6W3iz8plpuP+RQ4Me4Iljm7O5aPDp0SSrIoZxyX4zS668mVAoz5HHj1Ka0kQkft8mq26Q==}
- dependencies:
- '@lexical/code-core': 0.44.0
- '@lexical/link': 0.44.0
- '@lexical/list': 0.44.0
- '@lexical/rich-text': 0.44.0
- '@lexical/text': 0.44.0
- '@lexical/utils': 0.44.0
- lexical: 0.44.0
- dev: false
- /@lexical/overflow@0.44.0:
+ '@lexical/overflow@0.44.0':
resolution: {integrity: sha512-5GYaYjSxn27pqHRfU+tQ2STF10wgJvI+MUnwTnUFSzy3dko1b+oV94K/Yx0TuEewPbwDibfoFA8CwqUvOLHAyw==}
- dependencies:
- lexical: 0.44.0
- dev: false
- /@lexical/plain-text@0.44.0:
+ '@lexical/plain-text@0.44.0':
resolution: {integrity: sha512-bIV4Lljk0x70zFhkZIwzSPK5q3m9FpDisjGm2/3Q/chb+5BW3Tv8QJmqnpCiSO6S2KXO7gfSy81ZfkQ1dcd4EQ==}
- dependencies:
- '@lexical/clipboard': 0.44.0
- '@lexical/dragon': 0.44.0
- '@lexical/selection': 0.44.0
- '@lexical/utils': 0.44.0
- lexical: 0.44.0
- dev: false
- /@lexical/react@0.44.0(react-dom@19.2.5)(react@19.2.5)(yjs@13.6.30):
+ '@lexical/react@0.44.0':
resolution: {integrity: sha512-p/NQd/fMh3pXb1XqegE2ruvWDcUmfB12OidQ9nwtMtj5VfcUjQu2I+trUhgGRIADxSYxMWmw+8PPj5YSf4m5oA==}
peerDependencies:
react: '>=17.x'
@@ -826,89 +790,114 @@ packages:
peerDependenciesMeta:
yjs:
optional: true
- dependencies:
- '@floating-ui/react': 0.27.19(react-dom@19.2.5)(react@19.2.5)
- '@lexical/devtools-core': 0.44.0(react-dom@19.2.5)(react@19.2.5)
- '@lexical/dragon': 0.44.0
- '@lexical/extension': 0.44.0
- '@lexical/hashtag': 0.44.0
- '@lexical/history': 0.44.0
- '@lexical/link': 0.44.0
- '@lexical/list': 0.44.0
- '@lexical/mark': 0.44.0
- '@lexical/markdown': 0.44.0
- '@lexical/overflow': 0.44.0
- '@lexical/plain-text': 0.44.0
- '@lexical/rich-text': 0.44.0
- '@lexical/table': 0.44.0
- '@lexical/text': 0.44.0
- '@lexical/utils': 0.44.0
- '@lexical/yjs': 0.44.0(yjs@13.6.30)
- lexical: 0.44.0
- react: 19.2.5
- react-dom: 19.2.5(react@19.2.5)
- react-error-boundary: 6.1.1(react@19.2.5)
- yjs: 13.6.30
- dev: false
- /@lexical/rich-text@0.44.0:
+ '@lexical/rich-text@0.44.0':
resolution: {integrity: sha512-IIdrutK5GY47ITjPlZB7KzUi9dBDwygsyFOwolnrYSL7m6TtGhAqrYiFg/YNOTT/nBzK3KQeCJRbnxpjJAVZtQ==}
- dependencies:
- '@lexical/clipboard': 0.44.0
- '@lexical/dragon': 0.44.0
- '@lexical/selection': 0.44.0
- '@lexical/utils': 0.44.0
- lexical: 0.44.0
- dev: false
- /@lexical/selection@0.44.0:
+ '@lexical/selection@0.44.0':
resolution: {integrity: sha512-AEyeZJFFr5YRLeqVR+X0QAW19c4Fk4MFAQu52z2gxAyDGTj9xwVJxjfepVpfUp4P9K+sPtJ/yaqfMXH506ksSQ==}
- dependencies:
- lexical: 0.44.0
- dev: false
- /@lexical/table@0.44.0:
+ '@lexical/table@0.44.0':
resolution: {integrity: sha512-5Uq0O/fBCxcZp9y17fXUONY7dU9lVo/mB5JHy23laIiKzBKP5IzzTLMU9ikZTppIXbMNxYXd+R2pmy7PYTLyvw==}
- dependencies:
- '@lexical/clipboard': 0.44.0
- '@lexical/extension': 0.44.0
- '@lexical/utils': 0.44.0
- lexical: 0.44.0
- dev: false
- /@lexical/text@0.44.0:
+ '@lexical/text@0.44.0':
resolution: {integrity: sha512-1XJD8ZbwaXljTl8k4+jjiopdhnYZm26IJw9Gv8+cIThVC0b6B3JZ/WxH97BMDcSloKvWHFkGiPztxRwNwA29Rw==}
- dependencies:
- lexical: 0.44.0
- dev: false
- /@lexical/utils@0.44.0:
+ '@lexical/utils@0.44.0':
resolution: {integrity: sha512-/D2ptztNevfBJgtkj4uaiYBeRcvSy+1mQj6pNYaCFZIoPJIwl6H5fXwWAvpvr11vcQKP9DEEoXR+V4qkMOA+EA==}
- dependencies:
- '@lexical/selection': 0.44.0
- lexical: 0.44.0
- dev: false
- /@lexical/yjs@0.44.0(yjs@13.6.30):
+ '@lexical/yjs@0.44.0':
resolution: {integrity: sha512-b3QTub9J/3LuwSSdooynb6GbMHBRyBT4xUbXzXqNPbDHgYe6CDrqf/uJIHRihIjAhOnPaHYqo9XUzitl++N1DQ==}
peerDependencies:
yjs: '>=13.5.22'
- dependencies:
- '@lexical/selection': 0.44.0
- lexical: 0.44.0
- yjs: 13.6.30
- dev: false
- /@mjackson/node-fetch-server@0.2.0:
+ '@lexical/clipboard@0.44.0':
+ resolution: {integrity: sha512-nfmNIs7uENqlDI7cm2E4I1Yp8mDJGMhEQIrIV2rNWnL1oeHVXQ7yuYdyoPdcY1zuj/9nvkYBQYUEh0QiGwpETA==}
+
+ '@lexical/code-core@0.44.0':
+ resolution: {integrity: sha512-m57JyXTIvW1tsqw/Vuogk8jqWCZZIeFQbWybRc46ytR8ReDgzPRODpN8+dacIIeRH5yC5UC3lAa743mtdNkxqg==}
+
+ '@lexical/devtools-core@0.44.0':
+ resolution: {integrity: sha512-X3uNG3P1vOsdzmEcy+7m9DxAcIVtVUZnvskmLqqLs6VluVVwH9xy7h1bPsvlDKvj1Nj73tWJ3TW0qXQWDTo5tw==}
+ peerDependencies:
+ react: '>=17.x'
+ react-dom: '>=17.x'
+
+ '@lexical/dragon@0.44.0':
+ resolution: {integrity: sha512-RhlsjVDket9k1+YFEkDE0/7Qyrh2BI0vxBMzrWwPJTXX/4YFanYN9su8RSabkIukBBJ3QiNOOoC8FKK4Lkr4qg==}
+
+ '@lexical/extension@0.44.0':
+ resolution: {integrity: sha512-BsYtoc+0EU0pqcOpf/lIUDU6LQVO6zX2AawZoUWJzT3Wzfov23qsqZWvl2WGM9dnRTN5iISJL3Fl53bQVxiXxw==}
+
+ '@lexical/hashtag@0.44.0':
+ resolution: {integrity: sha512-0WATahDSqYKVTudQv3KpFbLeCpmrCpRptPFbjxOMckAX2MRpYlrExlqKfgfpri5BSQPtG49EPSGeNfSx/Faavw==}
+
+ '@lexical/history@0.44.0':
+ resolution: {integrity: sha512-RGXcbFTgYL1GIWaReBI26mNSsJTfiA9EAtDY4LBeZ14NrIQhYNokKgNiOxq5Bn8xXrl2+mawQEqoMfgpWp/5YA==}
+
+ '@lexical/html@0.44.0':
+ resolution: {integrity: sha512-5X6eGsgwtqPxABsuShUxF7ZfyB/U4GwSEyeonvwH1Vc/5Q2uQVjlB+FAYd+MNwWMHMh4d4+yZ3l70AtIuhr5eg==}
+
+ '@lexical/link@0.44.0':
+ resolution: {integrity: sha512-uvEqEol/mLEzGVQd8Rok9I48RgYPKokM/nsclI9nYcEdccVOM2Nri4ntoRwodhbccFLtjMPl8OBldwXbfc77tQ==}
+
+ '@lexical/list@0.44.0':
+ resolution: {integrity: sha512-ZTCWxDz1okPrC9FBXi1yV3W5fbQQeMUlFIcSVF9HibcVPmCsPa900IxthuiQbGiTycUyXDTOB3IUYRtlJNtpjw==}
+
+ '@lexical/mark@0.44.0':
+ resolution: {integrity: sha512-bWMowllwe6BcgYMAkrsZx6Z+CX/72qCQpFKhlkR4ael92yOWSBkz68xp1wxxkSnQX9zoI1gYTeWBofVsSDKcsQ==}
+
+ '@lexical/markdown@0.44.0':
+ resolution: {integrity: sha512-DwlXdp85pYMo3exDF6W3iz8plpuP+RQ4Me4Iljm7O5aPDp0SSrIoZxyX4zS668mVAoz5HHj1Ka0kQkft8mq26Q==}
+
+ '@lexical/overflow@0.44.0':
+ resolution: {integrity: sha512-5GYaYjSxn27pqHRfU+tQ2STF10wgJvI+MUnwTnUFSzy3dko1b+oV94K/Yx0TuEewPbwDibfoFA8CwqUvOLHAyw==}
+
+ '@lexical/plain-text@0.44.0':
+ resolution: {integrity: sha512-bIV4Lljk0x70zFhkZIwzSPK5q3m9FpDisjGm2/3Q/chb+5BW3Tv8QJmqnpCiSO6S2KXO7gfSy81ZfkQ1dcd4EQ==}
+
+ '@lexical/react@0.44.0':
+ resolution: {integrity: sha512-p/NQd/fMh3pXb1XqegE2ruvWDcUmfB12OidQ9nwtMtj5VfcUjQu2I+trUhgGRIADxSYxMWmw+8PPj5YSf4m5oA==}
+ peerDependencies:
+ react: '>=17.x'
+ react-dom: '>=17.x'
+ yjs: '>=13.5.22'
+ peerDependenciesMeta:
+ yjs:
+ optional: true
+
+ '@lexical/rich-text@0.44.0':
+ resolution: {integrity: sha512-IIdrutK5GY47ITjPlZB7KzUi9dBDwygsyFOwolnrYSL7m6TtGhAqrYiFg/YNOTT/nBzK3KQeCJRbnxpjJAVZtQ==}
+
+ '@lexical/selection@0.44.0':
+ resolution: {integrity: sha512-AEyeZJFFr5YRLeqVR+X0QAW19c4Fk4MFAQu52z2gxAyDGTj9xwVJxjfepVpfUp4P9K+sPtJ/yaqfMXH506ksSQ==}
+
+ '@lexical/table@0.44.0':
+ resolution: {integrity: sha512-5Uq0O/fBCxcZp9y17fXUONY7dU9lVo/mB5JHy23laIiKzBKP5IzzTLMU9ikZTppIXbMNxYXd+R2pmy7PYTLyvw==}
+
+ '@lexical/text@0.44.0':
+ resolution: {integrity: sha512-1XJD8ZbwaXljTl8k4+jjiopdhnYZm26IJw9Gv8+cIThVC0b6B3JZ/WxH97BMDcSloKvWHFkGiPztxRwNwA29Rw==}
+
+ '@lexical/utils@0.44.0':
+ resolution: {integrity: sha512-/D2ptztNevfBJgtkj4uaiYBeRcvSy+1mQj6pNYaCFZIoPJIwl6H5fXwWAvpvr11vcQKP9DEEoXR+V4qkMOA+EA==}
+
+ '@lexical/yjs@0.44.0':
+ resolution: {integrity: sha512-b3QTub9J/3LuwSSdooynb6GbMHBRyBT4xUbXzXqNPbDHgYe6CDrqf/uJIHRihIjAhOnPaHYqo9XUzitl++N1DQ==}
+ peerDependencies:
+ yjs: '>=13.5.22'
+
+ '@mjackson/node-fetch-server@0.2.0':
resolution: {integrity: sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==}
dev: true
- /@preact/signals-core@1.14.1:
+ '@preact/signals-core@1.14.1':
+ resolution: {integrity: sha512-vxPpfXqrwUe9lpjqfYNjAF/0RF/eFGeLgdJzdmIIZjpOnTmGmAB4BjWone562mJGMRP4frU6iZ6ei3PDsu52Ng==}
+
+ '@preact/signals-core@1.14.1':
resolution: {integrity: sha512-vxPpfXqrwUe9lpjqfYNjAF/0RF/eFGeLgdJzdmIIZjpOnTmGmAB4BjWone562mJGMRP4frU6iZ6ei3PDsu52Ng==}
- dev: false
- /@react-router/dev@7.14.2(react-router@7.14.2)(typescript@5.9.3)(vite@6.4.2):
- resolution: {integrity: sha512-lU88Ls4iC78RdPOKkER54+hlsHzzS8WSZrf2/cGQumbIN2A5WvO0LDyv72cdJmLWujgZ9rpNoGzmqWINssShGQ==}
+ '@react-router/dev@7.13.0':
+ resolution: {integrity: sha512-0vRfTrS6wIXr9j0STu614Cv2ytMr21evnv1r+DXPv5cJ4q0V2x2kBAXC8TAqEXkpN5vdhbXBlbGQ821zwOfhvg==}
engines: {node: '>=20.0.0'}
hasBin: true
peerDependencies:
@@ -1089,105 +1078,66 @@ packages:
resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==}
cpu: [arm]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@rollup/rollup-linux-arm-musleabihf@4.60.2:
resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==}
cpu: [arm]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@rollup/rollup-linux-arm64-gnu@4.60.2:
resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==}
cpu: [arm64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@rollup/rollup-linux-arm64-musl@4.60.2:
resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==}
cpu: [arm64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@rollup/rollup-linux-loong64-gnu@4.60.2:
resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==}
cpu: [loong64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@rollup/rollup-linux-loong64-musl@4.60.2:
resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==}
cpu: [loong64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@rollup/rollup-linux-ppc64-gnu@4.60.2:
resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==}
cpu: [ppc64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@rollup/rollup-linux-ppc64-musl@4.60.2:
resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==}
cpu: [ppc64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@rollup/rollup-linux-riscv64-gnu@4.60.2:
resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==}
cpu: [riscv64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@rollup/rollup-linux-riscv64-musl@4.60.2:
resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==}
cpu: [riscv64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@rollup/rollup-linux-s390x-gnu@4.60.2:
resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==}
cpu: [s390x]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@rollup/rollup-linux-x64-gnu@4.60.2:
resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==}
cpu: [x64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@rollup/rollup-linux-x64-musl@4.60.2:
resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==}
cpu: [x64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@rollup/rollup-openbsd-x64@4.60.2:
resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==}
@@ -1367,36 +1317,24 @@ packages:
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@tailwindcss/oxide-linux-arm64-musl@4.2.4:
resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@tailwindcss/oxide-linux-x64-gnu@4.2.4:
resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@tailwindcss/oxide-linux-x64-musl@4.2.4:
resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/@tailwindcss/oxide-wasm32-wasi@4.2.4:
resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==}
@@ -1577,11 +1515,13 @@ packages:
dependencies:
csstype: 3.2.3
- /@types/trusted-types@2.0.7:
+ '@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
- dev: false
- /@types/use-sync-external-store@0.0.6:
+ '@types/trusted-types@2.0.7':
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
+ '@types/use-sync-external-store@0.0.6':
resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
dev: false
@@ -1989,11 +1929,13 @@ packages:
engines: {node: '>=18'}
dev: true
- /isomorphic.js@0.2.5:
+ isomorphic.js@0.2.5:
resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
- dev: false
- /jiti@2.6.1:
+ isomorphic.js@0.2.5:
+ resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
+
+ jiti@2.6.1:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
dev: true
@@ -2014,20 +1956,24 @@ packages:
hasBin: true
dev: true
- /lexical@0.44.0:
+ lexical@0.44.0:
resolution: {integrity: sha512-ReDUjRlFgkGoPWzvdjr7s16PUVpHATN+2NH2NiZs+PLlISTaIFFgKil2P467oP3Vg+XgmpDsUgmWZsFJTztYjg==}
- dev: false
- /lib0@0.2.117:
+ lib0@0.2.117:
+ resolution: {integrity: sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==}
+ engines: {node: '>=16'}
+ hasBin: true
+
+ lexical@0.44.0:
+ resolution: {integrity: sha512-ReDUjRlFgkGoPWzvdjr7s16PUVpHATN+2NH2NiZs+PLlISTaIFFgKil2P467oP3Vg+XgmpDsUgmWZsFJTztYjg==}
+
+ lib0@0.2.117:
resolution: {integrity: sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==}
engines: {node: '>=16'}
hasBin: true
- dependencies:
- isomorphic.js: 0.2.5
- dev: false
- /lightningcss-android-arm64@1.32.0:
- resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
+ lightningcss-android-arm64@1.30.2:
+ resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [android]
@@ -2076,36 +2022,24 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/lightningcss-linux-arm64-musl@1.32.0:
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/lightningcss-linux-x64-gnu@1.32.0:
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/lightningcss-linux-x64-musl@1.32.0:
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
- requiresBuild: true
- dev: true
- optional: true
/lightningcss-win32-arm64-msvc@1.32.0:
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
@@ -2234,13 +2168,18 @@ packages:
react: 19.2.5
scheduler: 0.27.0
- /react-error-boundary@6.1.1(react@19.2.5):
+ react-error-boundary@6.1.1:
+ resolution: {integrity: sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w==}
+ peerDependencies:
+ react: ^18.0.0 || ^19.0.0
+
+ react-error-boundary@6.1.1:
resolution: {integrity: sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
- dependencies:
- react: 19.2.5
- dev: false
+
+ react-is@19.2.4:
+ resolution: {integrity: sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==}
/react-is@19.2.5:
resolution: {integrity: sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==}
@@ -2399,9 +2338,14 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /tabbable@6.4.0:
+ tabbable@6.4.0:
resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==}
- dev: false
+
+ tabbable@6.4.0:
+ resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==}
+
+ tailwindcss@4.1.18:
+ resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
/tailwindcss@4.2.4:
resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==}
@@ -2579,14 +2523,15 @@ packages:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
dev: true
- /yjs@13.6.30:
+ yjs@13.6.30:
resolution: {integrity: sha512-vv/9h42eCMC81ZHDFswuu/MKzkl/vyq1BhaNGfHyOonwlG4CJbQF4oiBBJPvfdeCt/PlVDWh7Nov9D34YY09uQ==}
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
- dependencies:
- lib0: 0.2.117
- dev: false
- /zustand@4.5.7(@types/react@19.2.14)(react@19.2.5):
+ yjs@13.6.30:
+ resolution: {integrity: sha512-vv/9h42eCMC81ZHDFswuu/MKzkl/vyq1BhaNGfHyOonwlG4CJbQF4oiBBJPvfdeCt/PlVDWh7Nov9D34YY09uQ==}
+ engines: {node: '>=16.0.0', npm: '>=8.0.0'}
+
+ zustand@4.5.7:
resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==}
engines: {node: '>=12.7.0'}
peerDependencies:
@@ -2601,7 +2546,1365 @@ packages:
react:
optional: true
dependencies:
- '@types/react': 19.2.14
- react: 19.2.5
- use-sync-external-store: 1.6.0(react@19.2.5)
- dev: false
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.29.0': {}
+
+ '@babel/core@7.29.0':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/helpers': 7.28.6
+ '@babel/parser': 7.29.0
+ '@babel/template': 7.28.6
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.29.1':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.0.2
+
+ '@babel/helper-annotate-as-pure@7.27.3':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@babel/helper-compilation-targets@7.28.6':
+ dependencies:
+ '@babel/compat-data': 7.29.0
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.28.1
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-member-expression-to-functions': 7.28.5
+ '@babel/helper-optimise-call-expression': 7.27.1
+ '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/traverse': 7.29.0
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-member-expression-to-functions@7.28.5':
+ dependencies:
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-imports@7.28.6':
+ dependencies:
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-module-imports': 7.28.6
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-optimise-call-expression@7.27.1':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@babel/helper-plugin-utils@7.28.6': {}
+
+ '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-member-expression-to-functions': 7.28.5
+ '@babel/helper-optimise-call-expression': 7.27.1
+ '@babel/traverse': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.28.5': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helpers@7.28.6':
+ dependencies:
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
+
+ '@babel/parser@7.29.0':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-validator-option': 7.27.1
+ '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/template@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+
+ '@babel/traverse@7.29.0':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.29.0
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.29.0':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
+ '@esbuild/aix-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm@0.25.12':
+ optional: true
+
+ '@esbuild/android-x64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.12':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.12':
+ optional: true
+
+ '@floating-ui/core@1.7.5':
+ dependencies:
+ '@floating-ui/utils': 0.2.11
+
+ '@floating-ui/dom@1.7.6':
+ dependencies:
+ '@floating-ui/core': 1.7.5
+ '@floating-ui/utils': 0.2.11
+
+ '@floating-ui/react-dom@2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@floating-ui/dom': 1.7.6
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+
+ '@floating-ui/react@0.27.19(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@floating-ui/utils': 0.2.11
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ tabbable: 6.4.0
+
+ '@floating-ui/utils@0.2.11': {}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@lexical/clipboard@0.44.0':
+ dependencies:
+ '@lexical/extension': 0.44.0
+ '@lexical/html': 0.44.0
+ '@lexical/list': 0.44.0
+ '@lexical/selection': 0.44.0
+ '@lexical/utils': 0.44.0
+ '@types/trusted-types': 2.0.7
+ lexical: 0.44.0
+
+ '@lexical/code-core@0.44.0':
+ dependencies:
+ '@lexical/extension': 0.44.0
+ lexical: 0.44.0
+
+ '@lexical/devtools-core@0.44.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@lexical/html': 0.44.0
+ '@lexical/link': 0.44.0
+ '@lexical/mark': 0.44.0
+ '@lexical/table': 0.44.0
+ '@lexical/utils': 0.44.0
+ lexical: 0.44.0
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+
+ '@lexical/dragon@0.44.0':
+ dependencies:
+ '@lexical/extension': 0.44.0
+ lexical: 0.44.0
+
+ '@lexical/extension@0.44.0':
+ dependencies:
+ '@lexical/utils': 0.44.0
+ '@preact/signals-core': 1.14.1
+ lexical: 0.44.0
+
+ '@lexical/hashtag@0.44.0':
+ dependencies:
+ '@lexical/text': 0.44.0
+ '@lexical/utils': 0.44.0
+ lexical: 0.44.0
+
+ '@lexical/history@0.44.0':
+ dependencies:
+ '@lexical/extension': 0.44.0
+ '@lexical/utils': 0.44.0
+ lexical: 0.44.0
+
+ '@lexical/html@0.44.0':
+ dependencies:
+ '@lexical/extension': 0.44.0
+ '@lexical/selection': 0.44.0
+ '@lexical/utils': 0.44.0
+ lexical: 0.44.0
+
+ '@lexical/link@0.44.0':
+ dependencies:
+ '@lexical/extension': 0.44.0
+ '@lexical/utils': 0.44.0
+ lexical: 0.44.0
+
+ '@lexical/list@0.44.0':
+ dependencies:
+ '@lexical/extension': 0.44.0
+ '@lexical/utils': 0.44.0
+ lexical: 0.44.0
+
+ '@lexical/mark@0.44.0':
+ dependencies:
+ '@lexical/utils': 0.44.0
+ lexical: 0.44.0
+
+ '@lexical/markdown@0.44.0':
+ dependencies:
+ '@lexical/code-core': 0.44.0
+ '@lexical/link': 0.44.0
+ '@lexical/list': 0.44.0
+ '@lexical/rich-text': 0.44.0
+ '@lexical/text': 0.44.0
+ '@lexical/utils': 0.44.0
+ lexical: 0.44.0
+
+ '@lexical/overflow@0.44.0':
+ dependencies:
+ lexical: 0.44.0
+
+ '@lexical/plain-text@0.44.0':
+ dependencies:
+ '@lexical/clipboard': 0.44.0
+ '@lexical/dragon': 0.44.0
+ '@lexical/selection': 0.44.0
+ '@lexical/utils': 0.44.0
+ lexical: 0.44.0
+
+ '@lexical/react@0.44.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(yjs@13.6.30)':
+ dependencies:
+ '@floating-ui/react': 0.27.19(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@lexical/devtools-core': 0.44.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@lexical/dragon': 0.44.0
+ '@lexical/extension': 0.44.0
+ '@lexical/hashtag': 0.44.0
+ '@lexical/history': 0.44.0
+ '@lexical/link': 0.44.0
+ '@lexical/list': 0.44.0
+ '@lexical/mark': 0.44.0
+ '@lexical/markdown': 0.44.0
+ '@lexical/overflow': 0.44.0
+ '@lexical/plain-text': 0.44.0
+ '@lexical/rich-text': 0.44.0
+ '@lexical/table': 0.44.0
+ '@lexical/text': 0.44.0
+ '@lexical/utils': 0.44.0
+ '@lexical/yjs': 0.44.0(yjs@13.6.30)
+ lexical: 0.44.0
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ react-error-boundary: 6.1.1(react@19.2.4)
+ optionalDependencies:
+ yjs: 13.6.30
+
+ '@lexical/rich-text@0.44.0':
+ dependencies:
+ '@lexical/clipboard': 0.44.0
+ '@lexical/dragon': 0.44.0
+ '@lexical/selection': 0.44.0
+ '@lexical/utils': 0.44.0
+ lexical: 0.44.0
+
+ '@lexical/selection@0.44.0':
+ dependencies:
+ lexical: 0.44.0
+
+ '@lexical/table@0.44.0':
+ dependencies:
+ '@lexical/clipboard': 0.44.0
+ '@lexical/extension': 0.44.0
+ '@lexical/utils': 0.44.0
+ lexical: 0.44.0
+
+ '@lexical/text@0.44.0':
+ dependencies:
+ lexical: 0.44.0
+
+ '@lexical/utils@0.44.0':
+ dependencies:
+ '@lexical/selection': 0.44.0
+ lexical: 0.44.0
+
+ '@lexical/yjs@0.44.0(yjs@13.6.30)':
+ dependencies:
+ '@lexical/selection': 0.44.0
+ lexical: 0.44.0
+ yjs: 13.6.30
+
+ '@mjackson/node-fetch-server@0.2.0': {}
+
+ '@preact/signals-core@1.14.1': {}
+
+ '@react-router/dev@7.13.0(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2))':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/parser': 7.29.0
+ '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
+ '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0)
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ '@react-router/node': 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)
+ '@remix-run/node-fetch-server': 0.13.0
+ arg: 5.0.2
+ babel-dead-code-elimination: 1.0.12
+ chokidar: 4.0.3
+ dedent: 1.7.1
+ es-module-lexer: 1.7.0
+ exit-hook: 2.2.1
+ isbot: 5.1.34
+ jsesc: 3.0.2
+ lodash: 4.17.23
+ p-map: 7.0.4
+ pathe: 1.1.2
+ picocolors: 1.1.1
+ pkg-types: 2.3.0
+ prettier: 3.8.1
+ react-refresh: 0.14.2
+ react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ semver: 7.7.4
+ tinyglobby: 0.2.15
+ valibot: 1.2.0(typescript@5.9.3)
+ vite: 6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)
+ vite-node: 3.2.4(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)
+ optionalDependencies:
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - jiti
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+ '@react-router/fs-routes@7.13.0(@react-router/dev@7.13.0(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)))(typescript@5.9.3)':
+ dependencies:
+ '@react-router/dev': 7.13.0(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2))
+ minimatch: 9.0.5
+ optionalDependencies:
+ typescript: 5.9.3
+
+ '@react-router/node@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)':
+ dependencies:
+ '@mjackson/node-fetch-server': 0.2.0
+ react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ optionalDependencies:
+ typescript: 5.9.3
+
+ '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.13)(react@19.2.4)(redux@5.0.1))(react@19.2.4)':
+ dependencies:
+ '@standard-schema/spec': 1.1.0
+ '@standard-schema/utils': 0.3.0
+ immer: 11.1.3
+ redux: 5.0.1
+ redux-thunk: 3.1.0(redux@5.0.1)
+ reselect: 5.1.1
+ optionalDependencies:
+ react: 19.2.4
+ react-redux: 9.2.0(@types/react@19.2.13)(react@19.2.4)(redux@5.0.1)
+
+ '@remix-run/node-fetch-server@0.13.0': {}
+
+ '@rolldown/pluginutils@1.0.0-beta.27': {}
+
+ '@rollup/rollup-android-arm-eabi@4.57.1':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.57.1':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.57.1':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.57.1':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.57.1':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.57.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.57.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.57.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.57.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.57.1':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.57.1':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-musl@4.57.1':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.57.1':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-musl@4.57.1':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.57.1':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.57.1':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.57.1':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.57.1':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.57.1':
+ optional: true
+
+ '@rollup/rollup-openbsd-x64@4.57.1':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.57.1':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.57.1':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.57.1':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.57.1':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.57.1':
+ optional: true
+
+ '@standard-schema/spec@1.1.0': {}
+
+ '@standard-schema/utils@0.3.0': {}
+
+ '@supabase/auth-js@2.95.2':
+ dependencies:
+ tslib: 2.8.1
+
+ '@supabase/functions-js@2.95.2':
+ dependencies:
+ tslib: 2.8.1
+
+ '@supabase/postgrest-js@2.95.2':
+ dependencies:
+ tslib: 2.8.1
+
+ '@supabase/realtime-js@2.95.2':
+ dependencies:
+ '@types/phoenix': 1.6.7
+ '@types/ws': 8.18.1
+ tslib: 2.8.1
+ ws: 8.19.0
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
+ '@supabase/storage-js@2.95.2':
+ dependencies:
+ iceberg-js: 0.8.1
+ tslib: 2.8.1
+
+ '@supabase/supabase-js@2.95.2':
+ dependencies:
+ '@supabase/auth-js': 2.95.2
+ '@supabase/functions-js': 2.95.2
+ '@supabase/postgrest-js': 2.95.2
+ '@supabase/realtime-js': 2.95.2
+ '@supabase/storage-js': 2.95.2
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
+ '@tailwindcss/node@4.1.18':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.19.0
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+ magic-string: 0.30.21
+ source-map-js: 1.2.1
+ tailwindcss: 4.1.18
+
+ '@tailwindcss/oxide-android-arm64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide@4.1.18':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.1.18
+ '@tailwindcss/oxide-darwin-arm64': 4.1.18
+ '@tailwindcss/oxide-darwin-x64': 4.1.18
+ '@tailwindcss/oxide-freebsd-x64': 4.1.18
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.18
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.18
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.18
+ '@tailwindcss/oxide-wasm32-wasi': 4.1.18
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.18
+
+ '@tailwindcss/vite@4.1.18(vite@6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2))':
+ dependencies:
+ '@tailwindcss/node': 4.1.18
+ '@tailwindcss/oxide': 4.1.18
+ tailwindcss: 4.1.18
+ vite: 6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@types/d3-array@3.2.2': {}
+
+ '@types/d3-color@3.1.3': {}
+
+ '@types/d3-drag@3.0.7':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-ease@3.0.2': {}
+
+ '@types/d3-interpolate@3.0.4':
+ dependencies:
+ '@types/d3-color': 3.1.3
+
+ '@types/d3-path@3.1.1': {}
+
+ '@types/d3-scale@4.0.9':
+ dependencies:
+ '@types/d3-time': 3.0.4
+
+ '@types/d3-selection@3.0.11': {}
+
+ '@types/d3-shape@3.1.8':
+ dependencies:
+ '@types/d3-path': 3.1.1
+
+ '@types/d3-time@3.0.4': {}
+
+ '@types/d3-timer@3.0.2': {}
+
+ '@types/d3-transition@3.0.9':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-zoom@3.0.8':
+ dependencies:
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-selection': 3.0.11
+
+ '@types/estree@1.0.8': {}
+
+ '@types/node@25.2.1':
+ dependencies:
+ undici-types: 7.16.0
+
+ '@types/phoenix@1.6.7': {}
+
+ '@types/react-dom@19.2.3(@types/react@19.2.13)':
+ dependencies:
+ '@types/react': 19.2.13
+
+ '@types/react@19.2.13':
+ dependencies:
+ csstype: 3.2.3
+
+ '@types/trusted-types@2.0.7': {}
+
+ '@types/use-sync-external-store@0.0.6': {}
+
+ '@types/ws@8.18.1':
+ dependencies:
+ '@types/node': 25.2.1
+
+ '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2))':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0)
+ '@rolldown/pluginutils': 1.0.0-beta.27
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.17.0
+ vite: 6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@xyflow/react@12.10.0(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@xyflow/system': 0.0.74
+ classcat: 5.0.5
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ zustand: 4.5.7(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+
+ '@xyflow/system@0.0.74':
+ dependencies:
+ '@types/d3-drag': 3.0.7
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-selection': 3.0.11
+ '@types/d3-transition': 3.0.9
+ '@types/d3-zoom': 3.0.8
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-zoom: 3.0.0
+
+ arg@5.0.2: {}
+
+ babel-dead-code-elimination@1.0.12:
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/parser': 7.29.0
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ balanced-match@1.0.2: {}
+
+ baseline-browser-mapping@2.9.19: {}
+
+ brace-expansion@2.0.2:
+ dependencies:
+ balanced-match: 1.0.2
+
+ browserslist@4.28.1:
+ dependencies:
+ baseline-browser-mapping: 2.9.19
+ caniuse-lite: 1.0.30001768
+ electron-to-chromium: 1.5.286
+ node-releases: 2.0.27
+ update-browserslist-db: 1.2.3(browserslist@4.28.1)
+
+ cac@6.7.14: {}
+
+ caniuse-lite@1.0.30001768: {}
+
+ chokidar@4.0.3:
+ dependencies:
+ readdirp: 4.1.2
+
+ classcat@5.0.5: {}
+
+ clsx@2.1.1: {}
+
+ confbox@0.2.2: {}
+
+ convert-source-map@2.0.0: {}
+
+ cookie@1.1.1: {}
+
+ csstype@3.2.3: {}
+
+ d3-array@3.2.4:
+ dependencies:
+ internmap: 2.0.3
+
+ d3-color@3.1.0: {}
+
+ d3-dispatch@3.0.1: {}
+
+ d3-drag@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-selection: 3.0.0
+
+ d3-ease@3.0.1: {}
+
+ d3-format@3.1.2: {}
+
+ d3-interpolate@3.0.1:
+ dependencies:
+ d3-color: 3.1.0
+
+ d3-path@3.1.0: {}
+
+ d3-scale@4.0.2:
+ dependencies:
+ d3-array: 3.2.4
+ d3-format: 3.1.2
+ d3-interpolate: 3.0.1
+ d3-time: 3.1.0
+ d3-time-format: 4.1.0
+
+ d3-selection@3.0.0: {}
+
+ d3-shape@3.2.0:
+ dependencies:
+ d3-path: 3.1.0
+
+ d3-time-format@4.1.0:
+ dependencies:
+ d3-time: 3.1.0
+
+ d3-time@3.1.0:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-timer@3.0.1: {}
+
+ d3-transition@3.0.1(d3-selection@3.0.0):
+ dependencies:
+ d3-color: 3.1.0
+ d3-dispatch: 3.0.1
+ d3-ease: 3.0.1
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-timer: 3.0.1
+
+ d3-zoom@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ decimal.js-light@2.5.1: {}
+
+ dedent@1.7.1: {}
+
+ detect-libc@2.1.2: {}
+
+ electron-to-chromium@1.5.286: {}
+
+ enhanced-resolve@5.19.0:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.3.0
+
+ es-module-lexer@1.7.0: {}
+
+ es-toolkit@1.44.0: {}
+
+ esbuild@0.25.12:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.12
+ '@esbuild/android-arm': 0.25.12
+ '@esbuild/android-arm64': 0.25.12
+ '@esbuild/android-x64': 0.25.12
+ '@esbuild/darwin-arm64': 0.25.12
+ '@esbuild/darwin-x64': 0.25.12
+ '@esbuild/freebsd-arm64': 0.25.12
+ '@esbuild/freebsd-x64': 0.25.12
+ '@esbuild/linux-arm': 0.25.12
+ '@esbuild/linux-arm64': 0.25.12
+ '@esbuild/linux-ia32': 0.25.12
+ '@esbuild/linux-loong64': 0.25.12
+ '@esbuild/linux-mips64el': 0.25.12
+ '@esbuild/linux-ppc64': 0.25.12
+ '@esbuild/linux-riscv64': 0.25.12
+ '@esbuild/linux-s390x': 0.25.12
+ '@esbuild/linux-x64': 0.25.12
+ '@esbuild/netbsd-arm64': 0.25.12
+ '@esbuild/netbsd-x64': 0.25.12
+ '@esbuild/openbsd-arm64': 0.25.12
+ '@esbuild/openbsd-x64': 0.25.12
+ '@esbuild/openharmony-arm64': 0.25.12
+ '@esbuild/sunos-x64': 0.25.12
+ '@esbuild/win32-arm64': 0.25.12
+ '@esbuild/win32-ia32': 0.25.12
+ '@esbuild/win32-x64': 0.25.12
+
+ escalade@3.2.0: {}
+
+ eventemitter3@5.0.4: {}
+
+ exit-hook@2.2.1: {}
+
+ exsolve@1.0.8: {}
+
+ fdir@6.5.0(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
+ fsevents@2.3.3:
+ optional: true
+
+ gensync@1.0.0-beta.2: {}
+
+ graceful-fs@4.2.11: {}
+
+ iceberg-js@0.8.1: {}
+
+ immer@10.2.0: {}
+
+ immer@11.1.3: {}
+
+ internmap@2.0.3: {}
+
+ isbot@5.1.34: {}
+
+ isomorphic.js@0.2.5: {}
+
+ jiti@2.6.1: {}
+
+ js-tokens@4.0.0: {}
+
+ jsesc@3.0.2: {}
+
+ json5@2.2.3: {}
+
+ lexical@0.44.0: {}
+
+ lib0@0.2.117:
+ dependencies:
+ isomorphic.js: 0.2.5
+
+ lightningcss-android-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-x64@1.30.2:
+ optional: true
+
+ lightningcss-freebsd-x64@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.30.2:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ optional: true
+
+ lightningcss@1.30.2:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.30.2
+ lightningcss-darwin-arm64: 1.30.2
+ lightningcss-darwin-x64: 1.30.2
+ lightningcss-freebsd-x64: 1.30.2
+ lightningcss-linux-arm-gnueabihf: 1.30.2
+ lightningcss-linux-arm64-gnu: 1.30.2
+ lightningcss-linux-arm64-musl: 1.30.2
+ lightningcss-linux-x64-gnu: 1.30.2
+ lightningcss-linux-x64-musl: 1.30.2
+ lightningcss-win32-arm64-msvc: 1.30.2
+ lightningcss-win32-x64-msvc: 1.30.2
+
+ lodash@4.17.23: {}
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ minimatch@9.0.5:
+ dependencies:
+ brace-expansion: 2.0.2
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.11: {}
+
+ node-releases@2.0.27: {}
+
+ p-map@7.0.4: {}
+
+ pathe@1.1.2: {}
+
+ pathe@2.0.3: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.3: {}
+
+ pkg-types@2.3.0:
+ dependencies:
+ confbox: 0.2.2
+ exsolve: 1.0.8
+ pathe: 2.0.3
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ prettier@3.8.1: {}
+
+ react-dom@19.2.4(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+ scheduler: 0.27.0
+
+ react-error-boundary@6.1.1(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+
+ react-is@19.2.4: {}
+
+ react-redux@9.2.0(@types/react@19.2.13)(react@19.2.4)(redux@5.0.1):
+ dependencies:
+ '@types/use-sync-external-store': 0.0.6
+ react: 19.2.4
+ use-sync-external-store: 1.6.0(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.13
+ redux: 5.0.1
+
+ react-refresh@0.14.2: {}
+
+ react-refresh@0.17.0: {}
+
+ react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
+ dependencies:
+ cookie: 1.1.1
+ react: 19.2.4
+ set-cookie-parser: 2.7.2
+ optionalDependencies:
+ react-dom: 19.2.4(react@19.2.4)
+
+ react@19.2.4: {}
+
+ readdirp@4.1.2: {}
+
+ recharts@3.7.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react-is@19.2.4)(react@19.2.4)(redux@5.0.1):
+ dependencies:
+ '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.2.13)(react@19.2.4)(redux@5.0.1))(react@19.2.4)
+ clsx: 2.1.1
+ decimal.js-light: 2.5.1
+ es-toolkit: 1.44.0
+ eventemitter3: 5.0.4
+ immer: 10.2.0
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ react-is: 19.2.4
+ react-redux: 9.2.0(@types/react@19.2.13)(react@19.2.4)(redux@5.0.1)
+ reselect: 5.1.1
+ tiny-invariant: 1.3.3
+ use-sync-external-store: 1.6.0(react@19.2.4)
+ victory-vendor: 37.3.6
+ transitivePeerDependencies:
+ - '@types/react'
+ - redux
+
+ redux-thunk@3.1.0(redux@5.0.1):
+ dependencies:
+ redux: 5.0.1
+
+ redux@5.0.1: {}
+
+ reselect@5.1.1: {}
+
+ rollup@4.57.1:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.57.1
+ '@rollup/rollup-android-arm64': 4.57.1
+ '@rollup/rollup-darwin-arm64': 4.57.1
+ '@rollup/rollup-darwin-x64': 4.57.1
+ '@rollup/rollup-freebsd-arm64': 4.57.1
+ '@rollup/rollup-freebsd-x64': 4.57.1
+ '@rollup/rollup-linux-arm-gnueabihf': 4.57.1
+ '@rollup/rollup-linux-arm-musleabihf': 4.57.1
+ '@rollup/rollup-linux-arm64-gnu': 4.57.1
+ '@rollup/rollup-linux-arm64-musl': 4.57.1
+ '@rollup/rollup-linux-loong64-gnu': 4.57.1
+ '@rollup/rollup-linux-loong64-musl': 4.57.1
+ '@rollup/rollup-linux-ppc64-gnu': 4.57.1
+ '@rollup/rollup-linux-ppc64-musl': 4.57.1
+ '@rollup/rollup-linux-riscv64-gnu': 4.57.1
+ '@rollup/rollup-linux-riscv64-musl': 4.57.1
+ '@rollup/rollup-linux-s390x-gnu': 4.57.1
+ '@rollup/rollup-linux-x64-gnu': 4.57.1
+ '@rollup/rollup-linux-x64-musl': 4.57.1
+ '@rollup/rollup-openbsd-x64': 4.57.1
+ '@rollup/rollup-openharmony-arm64': 4.57.1
+ '@rollup/rollup-win32-arm64-msvc': 4.57.1
+ '@rollup/rollup-win32-ia32-msvc': 4.57.1
+ '@rollup/rollup-win32-x64-gnu': 4.57.1
+ '@rollup/rollup-win32-x64-msvc': 4.57.1
+ fsevents: 2.3.3
+
+ scheduler@0.27.0: {}
+
+ semver@6.3.1: {}
+
+ semver@7.7.4: {}
+
+ set-cookie-parser@2.7.2: {}
+
+ source-map-js@1.2.1: {}
+
+ tabbable@6.4.0: {}
+
+ tailwindcss@4.1.18: {}
+
+ tapable@2.3.0: {}
+
+ tiny-invariant@1.3.3: {}
+
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+
+ tslib@2.8.1: {}
+
+ typescript@5.9.3: {}
+
+ undici-types@7.16.0: {}
+
+ update-browserslist-db@1.2.3(browserslist@4.28.1):
+ dependencies:
+ browserslist: 4.28.1
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ use-sync-external-store@1.6.0(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+
+ valibot@1.2.0(typescript@5.9.3):
+ optionalDependencies:
+ typescript: 5.9.3
+
+ victory-vendor@37.3.6:
+ dependencies:
+ '@types/d3-array': 3.2.2
+ '@types/d3-ease': 3.0.2
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-scale': 4.0.9
+ '@types/d3-shape': 3.1.8
+ '@types/d3-time': 3.0.4
+ '@types/d3-timer': 3.0.2
+ d3-array: 3.2.4
+ d3-ease: 3.0.1
+ d3-interpolate: 3.0.1
+ d3-scale: 4.0.2
+ d3-shape: 3.2.0
+ d3-time: 3.1.0
+ d3-timer: 3.0.1
+
+ vite-node@3.2.4(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2):
+ dependencies:
+ cac: 6.7.14
+ debug: 4.4.3
+ es-module-lexer: 1.7.0
+ pathe: 2.0.3
+ vite: 6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2)
+ transitivePeerDependencies:
+ - '@types/node'
+ - jiti
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+ vite@6.4.1(@types/node@25.2.1)(jiti@2.6.1)(lightningcss@1.30.2):
+ dependencies:
+ esbuild: 0.25.12
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.57.1
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 25.2.1
+ fsevents: 2.3.3
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+
+ ws@8.19.0: {}
+
+ yallist@3.1.1: {}
+
+ yjs@13.6.30:
+ dependencies:
+ lib0: 0.2.117
+
+ zustand@4.5.7(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4):
+ dependencies:
+ use-sync-external-store: 1.6.0(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.13
+ immer: 11.1.3
+ react: 19.2.4
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index 4d664cf..80da511 100644
--- a/makima/frontend/src/lib/api.ts
+++ b/makima/frontend/src/lib/api.ts
@@ -3583,6 +3583,120 @@ export async function pickUpOrders(directiveId: string): Promise<PickUpOrdersRes
}
// =============================================================================
+// Directive Documents API
+//
+// A directive_document is one of N markdown documents owned by a directive.
+// Each has its own lifecycle (draft → active → shipped → archived) and may be
+// attached to a PR. The frontend never calls the /ship endpoint — the backend
+// invokes that itself when PRs are raised. Editing a shipped document
+// auto-reactivates it on the backend (see PATCH handler).
+// =============================================================================
+
+export type DirectiveDocumentStatus = "draft" | "active" | "shipped" | "archived";
+
+export interface DirectiveDocument {
+ id: string;
+ directiveId: string;
+ title: string;
+ body: string;
+ status: DirectiveDocumentStatus;
+ prUrl: string | null;
+ prBranch: string | null;
+ shippedAt: string | null;
+ archivedAt: string | null;
+ version: number;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface CreateDirectiveDocumentRequest {
+ title?: string;
+ body?: string;
+}
+
+export interface UpdateDirectiveDocumentRequest {
+ title?: string;
+ body?: string;
+}
+
+export async function listDirectiveDocuments(directiveId: string): Promise<DirectiveDocument[]> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/documents`);
+ if (!res.ok) throw new Error(`Failed to list directive documents: ${res.statusText}`);
+ return res.json();
+}
+
+export async function createDirectiveDocument(
+ directiveId: string,
+ req: CreateDirectiveDocumentRequest = {},
+): Promise<DirectiveDocument> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/documents`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(req),
+ });
+ if (!res.ok) throw new Error(`Failed to create directive document: ${res.statusText}`);
+ return res.json();
+}
+
+export async function getDirectiveDocument(documentId: string): Promise<DirectiveDocument> {
+ const res = await authFetch(`${API_BASE}/api/v1/directive-documents/${documentId}`);
+ if (!res.ok) throw new Error(`Failed to get directive document: ${res.statusText}`);
+ return res.json();
+}
+
+/**
+ * Update a directive document's title and/or body. The backend auto-reactivates
+ * a shipped document when its body changes (re-stamps status to `active`),
+ * which is why we don't expose a separate "reactivate" call.
+ */
+export async function updateDirectiveDocument(
+ documentId: string,
+ req: UpdateDirectiveDocumentRequest,
+): Promise<DirectiveDocument> {
+ const res = await authFetch(`${API_BASE}/api/v1/directive-documents/${documentId}`, {
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(req),
+ });
+ if (!res.ok) throw new Error(`Failed to update directive document: ${res.statusText}`);
+ return res.json();
+}
+
+export async function archiveDirectiveDocument(documentId: string): Promise<DirectiveDocument> {
+ const res = await authFetch(`${API_BASE}/api/v1/directive-documents/${documentId}/archive`, {
+ method: "POST",
+ });
+ if (!res.ok) throw new Error(`Failed to archive directive document: ${res.statusText}`);
+ return res.json();
+}
+
+/** Steps and tasks attached to a single directive document. Drives the
+ * per-document `tasks/` subfolder in the sidebar — when the document
+ * ships, its tasks visually move with it under shipped/. */
+export interface DocumentTasksResponse {
+ steps: DirectiveStep[];
+ tasks: Task[];
+}
+
+/**
+ * List the steps and ephemeral tasks attached to a specific directive
+ * document. Used by the sidebar to render a `tasks/` subfolder beside each
+ * document — including shipped documents, whose tasks remain attached so
+ * they continue to render under shipped/ alongside the document.
+ */
+export async function listDirectiveDocumentTasks(
+ documentId: string,
+): Promise<DocumentTasksResponse> {
+ const res = await authFetch(
+ `${API_BASE}/api/v1/directive-documents/${documentId}/tasks`,
+ );
+ if (!res.ok) {
+ throw new Error(`Failed to list directive document tasks: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+// =============================================================================
// Directive Order Groups (DOGs) API
// =============================================================================
diff --git a/makima/frontend/src/routes/document-directives.tsx b/makima/frontend/src/routes/document-directives.tsx
index a3ea969..b583bef 100644
--- a/makima/frontend/src/routes/document-directives.tsx
+++ b/makima/frontend/src/routes/document-directives.tsx
@@ -3,36 +3,20 @@ import { useNavigate, useParams, useSearchParams } from "react-router";
import { Masthead } from "../components/Masthead";
import { useDirective, useDirectives } from "../hooks/useDirectives";
import { useAuth } from "../contexts/AuthContext";
-import { useSupervisorQuestions } from "../contexts/SupervisorQuestionsContext";
import { DocumentEditor } from "../components/directives/DocumentEditor";
-import { DocumentTaskStream } from "../components/directives/DocumentTaskStream";
-import { DirectiveContextMenu } from "../components/directives/DirectiveContextMenu";
import {
- startDirective,
- pauseDirective,
- updateDirective,
- deleteDirective,
- completeDirectiveStep,
- failDirectiveStep,
- skipDirectiveStep,
- stopTask,
- listDirectiveRevisions,
- newDirectiveDraft,
- createDirectivePR,
- advanceDirective,
- cleanupDirective,
- pickUpOrders,
- sendTaskMessage,
- listDirectiveEphemeralTasks,
- createDirectiveTask,
- listOrphanTasks,
-} from "../lib/api";
-import type {
- DirectiveStatus,
- DirectiveSummary,
- DirectiveWithSteps,
- DirectiveRevision,
- TaskSummary,
+ type DirectiveSummary,
+ type DirectiveStatus,
+ type DirectiveDocument,
+ type DirectiveDocumentStatus,
+ type DirectiveStep,
+ type Task,
+ type DocumentTasksResponse,
+ listDirectiveDocuments,
+ createDirectiveDocument,
+ getDirectiveDocument,
+ updateDirectiveDocument,
+ listDirectiveDocumentTasks,
} from "../lib/api";
// Status dot color, matching the existing tabular UI's badge palette so the
@@ -46,17 +30,52 @@ const STATUS_DOT: Record<DirectiveStatus, string> = {
archived: "bg-[#3a4a6a]",
};
-// Per-task dot color for the sidebar entries inside a directive folder.
-// Matches the StepsBlockNode palette.
-const STEP_STATUS_DOT: Record<string, string> = {
- pending: "bg-[#556677]",
- ready: "bg-[#9bc3ff]",
- running: "bg-yellow-400",
- done: "bg-green-400",
- failed: "bg-red-400",
- skipped: "bg-[#3a4a6a]",
+// Per-document status palette. Active/draft documents use the same bright
+// green-ish accent as a running directive; shipped/archived use a muted blue.
+const DOC_STATUS_DOT: Record<DirectiveDocumentStatus, string> = {
+ draft: "bg-[#556677]",
+ active: "bg-green-400",
+ shipped: "bg-[#75aafc]",
+ archived: "bg-[#3a4a6a]",
+};
+
+// =============================================================================
+// Sidebar grouping — group directives by lifecycle stage so the file tree
+// reads like a folder per status. We collapse the noisy ones (Archived) by
+// default and keep Active / Idle expanded.
+// =============================================================================
+
+type SidebarGroup = "active" | "idle" | "archived";
+
+const GROUP_LABEL: Record<SidebarGroup, string> = {
+ active: "active",
+ idle: "idle",
+ archived: "archived",
};
+function bucketOf(status: DirectiveStatus): SidebarGroup {
+ if (status === "active" || status === "paused") return "active";
+ if (status === "archived") return "archived";
+ // draft + idle land in the idle bucket (i.e. "not currently running").
+ return "idle";
+}
+
+// Slugify a document title for the displayed `.md` filename, falling back to
+// the directive title and finally the document id slice when the title is
+// empty. Mirrors the file-naming fix from step 1 (use the user-readable label
+// rather than just an id slice). Accepts either a DirectiveSummary or a full
+// DirectiveWithSteps — only `title` is read.
+function fileLabel(
+ doc: DirectiveDocument,
+ directive: { title: string },
+): string {
+ const docTitle = doc.title.trim();
+ if (docTitle.length > 0) return docTitle;
+ const dirTitle = directive.title.trim();
+ if (dirTitle.length > 0) return dirTitle;
+ return doc.id.slice(0, 8);
+}
+
// =============================================================================
// Sidebar icons (inline SVG, no new deps)
// =============================================================================
@@ -112,103 +131,6 @@ function FileIcon() {
);
}
-/** Terminal/prompt icon for orchestrator and step tasks. */
-function TaskIcon() {
- return (
- <svg
- viewBox="0 0 16 16"
- width={12}
- height={12}
- className="shrink-0"
- aria-hidden
- >
- <rect x="1.5" y="3" width="13" height="10" rx="1" fill="none" stroke="#9bc3ff" strokeWidth="1" />
- <path d="M3.5 6l2 2-2 2 M7 10h4" stroke="#9bc3ff" strokeWidth="1" fill="none" strokeLinecap="round" />
- </svg>
- );
-}
-
-/** Asterisk-on-terminal icon for ephemeral spinoff tasks — visually
- distinct from the plain TaskIcon used for step-spawned execution tasks
- so users can tell at a glance which tasks are part of the DAG vs which
- are user-spun side quests. */
-function EphemeralTaskIcon() {
- return (
- <svg
- viewBox="0 0 16 16"
- width={12}
- height={12}
- className="shrink-0"
- aria-hidden
- >
- <rect x="1.5" y="3" width="13" height="10" rx="1" fill="none" stroke="#c084fc" strokeWidth="1" />
- <path d="M3.5 6l2 2-2 2 M7 10h4" stroke="#c084fc" strokeWidth="1" fill="none" strokeLinecap="round" />
- <path d="M11 4l1 1m-1 0l1-1" stroke="#c084fc" strokeWidth="1" fill="none" />
- </svg>
- );
-}
-
-/** PR-bracket icon for the completion task. */
-function CompletionIcon() {
- return (
- <svg
- viewBox="0 0 16 16"
- width={12}
- height={12}
- className="shrink-0"
- aria-hidden
- >
- <circle cx="4" cy="4" r="1.4" fill="none" stroke="#9bc3ff" strokeWidth="1" />
- <circle cx="4" cy="12" r="1.4" fill="none" stroke="#9bc3ff" strokeWidth="1" />
- <circle cx="12" cy="12" r="1.4" fill="none" stroke="#9bc3ff" strokeWidth="1" />
- <path d="M4 5.4v5.2 M4 12h6.6 M12 4l0 6.6" stroke="#9bc3ff" strokeWidth="1" fill="none" />
- </svg>
- );
-}
-
-function PinIcon() {
- return (
- <svg
- viewBox="0 0 16 16"
- width={10}
- height={10}
- className="shrink-0"
- aria-hidden
- >
- <path
- d="M8 1.5l1.6 3.6 3.9.4-2.95 2.7.85 3.9L8 10.2 4.6 12.1l.85-3.9L2.5 5.5l3.9-.4z"
- fill="#75aafc"
- opacity="0.7"
- />
- </svg>
- );
-}
-
-/** Tiny chip used for the inline directive-folder hover actions. */
-function FolderActionButton({
- children,
- title,
- onClick,
-}: {
- children: React.ReactNode;
- title: string;
- onClick: () => void;
-}) {
- return (
- <button
- type="button"
- title={title}
- onClick={(e) => {
- e.stopPropagation();
- onClick();
- }}
- className="w-5 h-5 flex items-center justify-center text-[10px] text-[#7788aa] hover:text-white hover:bg-[rgba(117,170,252,0.15)] rounded transition-colors"
- >
- {children}
- </button>
- );
-}
-
function Caret({ open }: { open: boolean }) {
return (
<svg
@@ -224,940 +146,652 @@ function Caret({ open }: { open: boolean }) {
}
// =============================================================================
-// Sidebar
-// =============================================================================
-
-// =============================================================================
-// Task row context menu — sits next to DirectiveContextMenu and offers the
-// task-level controls (interrupt for orchestrator/completion, complete/fail/
-// skip for step tasks).
+// SidebarSelection — exactly one of taskId/documentId is non-null. taskId is
+// reserved for a future "task selection" feature (we expose it in the URL
+// already so the param shape is stable). documentId picks one of the
+// directive's documents.
// =============================================================================
-interface TaskContextMenuProps {
- x: number;
- y: number;
- task: FolderTaskRow;
- onClose: () => void;
- onInterrupt: () => void;
- onComplete?: () => void;
- onFail?: () => void;
- onSkip?: () => void;
- /** Send a freeform message to the running task (same wire as the inline comment box). */
- onSendMessage?: () => void;
- /** Navigate to the standalone task page for full-screen control. */
- onOpenInTaskPage?: () => void;
-}
-
-function TaskContextMenu({
- x,
- y,
- task,
- onClose,
- onInterrupt,
- onComplete,
- onFail,
- onSkip,
- onSendMessage,
- onOpenInTaskPage,
-}: TaskContextMenuProps) {
- const ref = useRef<HTMLDivElement>(null);
-
- useEffect(() => {
- const click = (e: MouseEvent) => {
- if (ref.current && !ref.current.contains(e.target as Node)) onClose();
- };
- const key = (e: KeyboardEvent) => {
- if (e.key === "Escape") onClose();
- };
- document.addEventListener("mousedown", click);
- document.addEventListener("keydown", key);
- return () => {
- document.removeEventListener("mousedown", click);
- document.removeEventListener("keydown", key);
- };
- }, [onClose]);
-
- useEffect(() => {
- if (!ref.current) return;
- const rect = ref.current.getBoundingClientRect();
- if (rect.right > window.innerWidth) ref.current.style.left = `${x - rect.width}px`;
- if (rect.bottom > window.innerHeight) ref.current.style.top = `${y - rect.height}px`;
- }, [x, y]);
-
- const item =
- "w-full px-3 py-1.5 text-left text-xs font-mono text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.1)] flex items-center gap-2";
- const divider = "border-t border-[rgba(117,170,252,0.2)] my-1";
-
- // Interrupt is meaningful for live tasks (orchestrator-active or running steps).
- const showInterrupt =
- task.kind === "orchestrator-active" ||
- task.kind === "completion" ||
- task.status === "running";
- // Step lifecycle controls only apply to step tasks.
- const isStep = task.kind === "step";
- const showComplete = isStep && task.status !== "done";
- const showFail = isStep && task.status !== "failed";
- const showSkip = isStep && task.status !== "skipped";
-
- return (
- <div
- ref={ref}
- className="fixed z-50 bg-[#0a1628] border border-[rgba(117,170,252,0.3)] shadow-lg min-w-[180px]"
- style={{ left: x, top: y }}
- >
- <div className="px-3 py-1.5 text-[10px] font-mono text-[#556677] uppercase border-b border-[rgba(117,170,252,0.2)] truncate max-w-[220px]">
- {task.kind === "orchestrator-active" ? "Orchestrator" : task.kind === "completion" ? "Completion" : task.label}
- </div>
- {showInterrupt && (
- <button
- className={item}
- onClick={() => {
- onInterrupt();
- onClose();
- }}
- >
- <span className="text-amber-300">⏹</span>
- Interrupt
- </button>
- )}
- {(showComplete || showFail || showSkip) && <div className={divider} />}
- {showComplete && (
- <button
- className={item}
- onClick={() => {
- onComplete?.();
- onClose();
- }}
- >
- <span className="text-emerald-400">✓</span>
- Mark complete
- </button>
- )}
- {showFail && (
- <button
- className={item}
- onClick={() => {
- onFail?.();
- onClose();
- }}
- >
- <span className="text-red-400">✗</span>
- Mark failed
- </button>
- )}
- {showSkip && (
- <button
- className={item}
- onClick={() => {
- onSkip?.();
- onClose();
- }}
- >
- <span className="text-[#7788aa]">⤳</span>
- Skip
- </button>
- )}
-
- {/* Direct task-page actions: send-message and open-in-task-page mirror
- what the standalone /exec/:taskId page exposes. */}
- {(onSendMessage || onOpenInTaskPage) && <div className={divider} />}
- {onSendMessage && (
- <button
- className={item}
- onClick={() => {
- onSendMessage();
- onClose();
- }}
- >
- <span className="text-cyan-300">⌨</span>
- Send message
- </button>
- )}
- {onOpenInTaskPage && (
- <button
- className={item}
- onClick={() => {
- onOpenInTaskPage();
- onClose();
- }}
- >
- <span className="text-[#75aafc]">↗</span>
- Open in task page
- </button>
- )}
- </div>
- );
-}
-
-function slugify(title: string, fallback: string): string {
- const slug = title
- .trim()
- .replace(/\s+/g, "-")
- .replace(/[^a-zA-Z0-9._-]/g, "")
- .toLowerCase();
- return slug.length > 0 ? slug : fallback;
-}
-
interface SidebarSelection {
directiveId: string;
- /** null = the directive's document; otherwise a task id (orchestrator/step). */
taskId: string | null;
+ documentId: string | null;
+}
+
+// =============================================================================
+// Per-directive folder — renders as a collapsible folder containing the
+// directive's documents. Loads documents lazily on first open (mirroring the
+// pattern from step 1's DirectiveFolder, which fetched the full directive
+// only when expanded).
+// =============================================================================
+
+interface DirectiveFolderProps {
+ directive: DirectiveSummary;
+ open: boolean;
+ onToggle: () => void;
+ /** Called when the user clicks the folder header itself (after toggle). */
+ onHeaderClick: () => void;
+ selection: SidebarSelection | null;
+ onSelectDocument: (directiveId: string, doc: DirectiveDocument) => void;
+ onCreateDocument: (directive: DirectiveSummary) => Promise<void>;
+ /**
+ * Document refresh trigger — bumped externally so the folder refetches its
+ * document list after a create/update happens elsewhere. Primarily used so
+ * a freshly-created document shows up immediately.
+ */
+ refreshNonce: number;
}
-/**
- * Per-directive folder. Renders the directive as a collapsible folder whose
- * body is the pinned document entry (always first) followed by a `tasks/`
- * subfolder containing the orchestrator, completion, and step tasks.
- *
- * Status dot lives on the right side only (single-side, per the v2 design).
- * If a directive or task has a pending user question, its icon glows.
- */
function DirectiveFolder({
directive,
open,
onToggle,
+ onHeaderClick,
selection,
- onSelect,
- pendingTaskIds,
- hasPendingForDirective,
- onDirectiveContextMenu,
- onTaskContextMenu,
- onCreateTask,
- onQuickAction,
-}: {
- directive: DirectiveSummary;
- open: boolean;
- onToggle: () => void;
- selection: SidebarSelection | null;
- onSelect: (sel: SidebarSelection) => void;
- /** Set of task ids that currently have pending user questions. */
- pendingTaskIds: Set<string>;
- /** Whether any pending question is associated with this directive. */
- hasPendingForDirective: boolean;
- onDirectiveContextMenu: (e: React.MouseEvent, d: DirectiveSummary) => void;
- onTaskContextMenu: (e: React.MouseEvent, t: FolderTaskRow, directiveId: string) => void;
- /** Open the inline "+ New task" form for this directive. */
- onCreateTask: (d: DirectiveSummary) => void;
- /** Trigger a quick action (start/pause/PR) on the directive. */
- onQuickAction: (d: DirectiveSummary, action: "start" | "pause" | "pr") => void;
-}) {
+ onSelectDocument,
+ onCreateDocument,
+ refreshNonce,
+}: DirectiveFolderProps) {
const dotColor = STATUS_DOT[directive.status] ?? STATUS_DOT.draft;
- const fileName = `${slugify(directive.title, directive.id.slice(0, 8))}.md`;
+ const orchestratorRunning = !!directive.orchestratorTaskId;
- // Lazy fetch full directive (with steps) only when folder is open.
- const { directive: detailed } = useDirective(open ? directive.id : undefined);
+ // Documents fetched lazily on open. We deliberately scope the fetch to the
+ // open-state so closed folders don't pay the network cost on initial render.
+ const [docs, setDocs] = useState<DirectiveDocument[] | null>(null);
+ const [docsLoading, setDocsLoading] = useState(false);
+ const [docsError, setDocsError] = useState<string | null>(null);
- const docSelected =
- selection?.directiveId === directive.id && selection.taskId === null;
+ // shipped/ subfolder open state — independent of the directive folder.
+ const [shippedOpen, setShippedOpen] = useState(false);
- // Ephemeral tasks attached to this directive (no directive_step_id). Fetched
- // lazily when the folder opens; refetched whenever a poll lands on the
- // directive's detail (poll-driven freshness).
- const [ephemeralTasks, setEphemeralTasks] = useState<TaskSummary[]>([]);
- useEffect(() => {
- if (!open) return;
- let cancelled = false;
- listDirectiveEphemeralTasks(directive.id)
- .then((res) => {
- if (!cancelled) setEphemeralTasks(res.tasks);
- })
- .catch((err) => {
- // eslint-disable-next-line no-console
- console.warn("[makima] failed to load ephemeral tasks", err);
- });
- return () => {
- cancelled = true;
- };
- }, [open, directive.id, directive.updatedAt]);
+ // Whether a "+ New document" call is in flight (disables the button).
+ const [creating, setCreating] = useState(false);
- // Collect the tasks to surface in the folder body.
- const tasks = useMemo(
- () => collectTasks(detailed, directive, ephemeralTasks),
- [detailed, directive, ephemeralTasks],
- );
+ const refresh = useCallback(async () => {
+ setDocsLoading(true);
+ setDocsError(null);
+ try {
+ const list = await listDirectiveDocuments(directive.id);
+ setDocs(list);
+ } catch (e) {
+ setDocsError(e instanceof Error ? e.message : "Failed to load documents");
+ } finally {
+ setDocsLoading(false);
+ }
+ }, [directive.id]);
- const orchestratorRunning = !!directive.orchestratorTaskId;
- // Tasks subfolder open state — independent of the directive folder.
- const [tasksOpen, setTasksOpen] = useState<boolean>(true);
- // Revisions subfolder — collapsed by default since most contracts have
- // no merged history yet.
- const [revisionsOpen, setRevisionsOpen] = useState<boolean>(false);
- const [revisions, setRevisions] = useState<DirectiveRevision[]>([]);
- // Fetch revisions only when the parent folder is open. Re-fetch whenever
- // the directive's pr_url changes so a freshly-raised PR appears.
+ // Fetch on open; refetch when refreshNonce bumps and the folder is open.
useEffect(() => {
if (!open) return;
- let cancelled = false;
- listDirectiveRevisions(directive.id)
- .then((res) => {
- if (!cancelled) setRevisions(res.revisions);
- })
- .catch((err) => {
- // eslint-disable-next-line no-console
- console.warn("[makima] failed to load revisions", err);
- });
- return () => {
- cancelled = true;
- };
- }, [open, directive.id, directive.prUrl]);
-
- // Inline action buttons on the folder header — visible on hover (and when
- // the folder is open) so users don't have to right-click to discover the
- // primary directive controls. Mirrors a code-editor sidebar's affordance.
- const showStart =
- directive.status === "draft" ||
- directive.status === "paused" ||
- directive.status === "idle" ||
- directive.status === "inactive";
- const showPause = directive.status === "active";
+ void refresh();
+ }, [open, refresh, refreshNonce]);
+
+ // Split the documents into the two visual groups. Memoised so we don't
+ // recompute on every render.
+ const { activeDocs, shippedDocs } = useMemo(() => {
+ const active: DirectiveDocument[] = [];
+ const shipped: DirectiveDocument[] = [];
+ for (const d of docs ?? []) {
+ if (d.status === "shipped" || d.status === "archived") {
+ shipped.push(d);
+ } else {
+ active.push(d);
+ }
+ }
+ // Stable order: by createdAt ascending so the first row is the oldest doc.
+ active.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
+ shipped.sort((a, b) => (b.shippedAt ?? "").localeCompare(a.shippedAt ?? ""));
+ return { activeDocs: active, shippedDocs: shipped };
+ }, [docs]);
+
+ const handleCreate = useCallback(async () => {
+ if (creating) return;
+ setCreating(true);
+ try {
+ await onCreateDocument(directive);
+ // Refresh after creating so the new doc appears in the list.
+ await refresh();
+ } finally {
+ setCreating(false);
+ }
+ }, [creating, onCreateDocument, directive, refresh]);
+
+ // Selection helpers — used to highlight the currently-selected doc row.
+ const selectedDocumentId =
+ selection && selection.directiveId === directive.id
+ ? selection.documentId
+ : null;
return (
- <div className="select-none group/dir">
- <div
- className="w-full flex items-center gap-1.5 pl-3 pr-3 py-1 font-mono text-[11px] text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.05)]"
- onContextMenu={(e) => onDirectiveContextMenu(e, directive)}
+ <div className="select-none">
+ {/* Directive folder header */}
+ <button
+ type="button"
+ onClick={() => {
+ onToggle();
+ onHeaderClick();
+ }}
+ title={directive.title}
+ className="w-full flex items-center gap-1.5 pl-9 pr-3 py-1 font-mono text-[11px] text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.06)]"
>
- <button
- type="button"
- onClick={onToggle}
- title={directive.title}
- className="flex items-center gap-1.5 flex-1 min-w-0 text-left"
- >
- <Caret open={open} />
- <FolderIcon open={open} />
- <span className="truncate flex-1">{directive.title}</span>
- </button>
-
- {/* Hover/open-only action chips — discoverable replacement for the
- right-click menu. Right-click still works as a power-user fallback. */}
- <div
- className={`flex items-center gap-0.5 transition-opacity ${
- open
- ? "opacity-100"
- : "opacity-0 group-hover/dir:opacity-100"
- }`}
- >
- {showStart && (
- <FolderActionButton
- title="Start"
- onClick={() => onQuickAction(directive, "start")}
- >
- ▶
- </FolderActionButton>
- )}
- {showPause && (
- <FolderActionButton
- title="Pause"
- onClick={() => onQuickAction(directive, "pause")}
- >
- ❚❚
- </FolderActionButton>
- )}
- {directive.prUrl && (
- <FolderActionButton
- title="Open PR"
- onClick={() =>
- window.open(directive.prUrl ?? "", "_blank", "noreferrer")
- }
- >
- ↗
- </FolderActionButton>
- )}
- <FolderActionButton
- title="New task"
- onClick={() => onCreateTask(directive)}
- >
- +
- </FolderActionButton>
- </div>
-
- {/* Status dot — RIGHT side only. Glows when this directive has a
- pending user question, or pulses when the orchestrator is live. */}
- <StatusDot
- color={dotColor}
- live={orchestratorRunning}
- glow={hasPendingForDirective}
- status={directive.status}
+ <Caret open={open} />
+ <FolderIcon open={open} />
+ <span
+ className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${dotColor}`}
+ aria-hidden
/>
- </div>
+ <span className="truncate flex-1 text-left">
+ {directive.title.trim().length > 0
+ ? directive.title
+ : directive.id.slice(0, 8)}
+ /
+ </span>
+ {orchestratorRunning && (
+ <span
+ className="inline-block w-1.5 h-1.5 rounded-full shrink-0 bg-yellow-400 animate-pulse"
+ title="Orchestrator running"
+ aria-label="Orchestrator running"
+ />
+ )}
+ </button>
+ {/* Folder body — rendered only when open */}
{open && (
- <ul className="py-0.5">
- {/* Pinned document entry — always at the top of the folder. */}
- <li>
- <button
- type="button"
- onClick={() =>
- onSelect({ directiveId: directive.id, taskId: null })
- }
- className={`w-full text-left flex items-center gap-1.5 pl-8 pr-3 py-1 font-mono text-[11px] transition-colors ${
- docSelected
- ? "bg-[rgba(117,170,252,0.12)] text-white border-l-2 border-[#75aafc]"
- : "text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.06)] border-l-2 border-transparent"
- }`}
- >
- <PinIcon />
- <FileIcon />
- <span className="truncate flex-1">{fileName}</span>
- </button>
- </li>
-
- {/* tasks/ subfolder — collapsible, contains orchestrator/completion/steps. */}
- <li>
- <button
- type="button"
- onClick={() => setTasksOpen((p) => !p)}
- className="w-full flex items-center gap-1.5 pl-8 pr-3 py-1 font-mono text-[11px] text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.05)]"
- >
- <Caret open={tasksOpen} />
- <FolderIcon open={tasksOpen} />
- <span className="truncate flex-1 text-left">tasks/</span>
- {tasks.length > 0 && (
- <span className="text-[10px] text-[#556677]">{tasks.length}</span>
- )}
- </button>
-
- {tasksOpen && (
- <ul className="py-0.5">
- {tasks.length === 0 ? (
- <li className="pl-14 pr-3 py-1 font-mono text-[10px] text-[#556677]">
- No tasks yet
- </li>
- ) : (
- tasks.map((t) => {
- const isSelected =
- selection?.directiveId === directive.id &&
- selection?.taskId === t.taskId;
- const tdot = STEP_STATUS_DOT[t.status] ?? STEP_STATUS_DOT.pending;
- const live =
- t.status === "running" || t.kind === "orchestrator-active";
- const glow = pendingTaskIds.has(t.taskId);
- const Icon =
- t.kind === "completion"
- ? CompletionIcon
- : t.kind === "ephemeral"
- ? EphemeralTaskIcon
- : TaskIcon;
- return (
- <li key={t.taskId}>
- <button
- type="button"
- onClick={() =>
- onSelect({
- directiveId: directive.id,
- taskId: t.taskId,
- })
- }
- onContextMenu={(e) =>
- onTaskContextMenu(e, t, directive.id)
- }
- title={t.label}
- className={`w-full text-left flex items-center gap-1.5 pl-14 pr-3 py-1 font-mono text-[11px] transition-colors ${
- isSelected
- ? "bg-[rgba(117,170,252,0.12)] text-white border-l-2 border-[#75aafc]"
- : "text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.06)] border-l-2 border-transparent"
- }`}
- >
- <Icon />
- <span className="truncate flex-1">{t.label}</span>
- <StatusDot
- color={tdot}
- live={live}
- glow={glow}
- status={t.status}
- />
- </button>
- </li>
- );
- })
- )}
- </ul>
- )}
- </li>
+ <div className="py-0.5">
+ {docsLoading && !docs && (
+ <div className="pl-14 pr-3 py-1 font-mono text-[10px] text-[#556677]">
+ Loading documents…
+ </div>
+ )}
+ {docsError && (
+ <div className="pl-14 pr-3 py-1 font-mono text-[10px] text-red-400">
+ {docsError}
+ </div>
+ )}
- {/* revisions/ subfolder — per-PR frozen snapshots of this contract.
- Only rendered when there's at least one revision; otherwise the
- folder body would be a confusing empty placeholder. */}
- {revisions.length > 0 && (
- <li>
+ {/* Active group */}
+ {docs && (
+ <>
+ {/* + New document affordance — sits at the top of the active list
+ so the user can always reach it without scrolling past
+ existing docs. */}
<button
type="button"
- onClick={() => setRevisionsOpen((p) => !p)}
- className="w-full flex items-center gap-1.5 pl-8 pr-3 py-1 font-mono text-[11px] text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.05)]"
+ onClick={handleCreate}
+ disabled={creating}
+ className="w-full flex items-center gap-1.5 pl-14 pr-3 py-1 font-mono text-[11px] text-emerald-400 hover:bg-[rgba(74,222,128,0.06)] disabled:opacity-50"
+ title="Create a new document under this directive"
>
- <Caret open={revisionsOpen} />
- <FolderIcon open={revisionsOpen} />
- <span className="truncate flex-1 text-left">revisions/</span>
- <span className="text-[10px] text-[#556677]">
- {revisions.length}
- </span>
+ <span className="text-[12px] leading-none">+</span>
+ <span>New document</span>
</button>
- {revisionsOpen && (
- <ul className="py-0.5">
- {revisions.map((r) => {
- const isSelected =
- selection?.directiveId === directive.id &&
- selection?.taskId === `revision:${r.id}`;
- return (
- <li key={r.id}>
- <button
- type="button"
- onClick={() =>
- onSelect({
- directiveId: directive.id,
- taskId: `revision:${r.id}`,
- })
- }
- title={`v${r.version} · ${r.prState} · ${r.prUrl}`}
- className={`w-full text-left flex items-center gap-1.5 pl-14 pr-3 py-1 font-mono text-[11px] transition-colors ${
- isSelected
- ? "bg-[rgba(117,170,252,0.12)] text-white border-l-2 border-[#75aafc]"
- : "text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.06)] border-l-2 border-transparent"
- }`}
- >
- <FileIcon />
- <span className="truncate flex-1">
- v{r.version}.md
- </span>
- <RevisionStateBadge prState={r.prState} />
- </button>
- </li>
- );
- })}
- </ul>
+ {activeDocs.length === 0 && !docsLoading && (
+ <div className="pl-14 pr-3 py-1 font-mono text-[10px] text-[#556677] italic">
+ no active documents
+ </div>
+ )}
+
+ {activeDocs.map((doc) => (
+ // Each active document gets its own tasks/ subfolder
+ // immediately below it. Active docs default-open the
+ // folder so the user sees their live work without an
+ // extra click.
+ <div key={doc.id}>
+ <DocumentRow
+ doc={doc}
+ directive={directive}
+ selected={doc.id === selectedDocumentId}
+ onSelect={() => onSelectDocument(directive.id, doc)}
+ />
+ <DocumentTasksFolder
+ documentId={doc.id}
+ depth="normal"
+ defaultOpen={doc.status === "active"}
+ refreshNonce={refreshNonce}
+ />
+ </div>
+ ))}
+
+ {/* shipped/ subfolder — only rendered when at least one shipped
+ or archived doc exists. Hidden entirely otherwise so empty
+ directives stay tidy. */}
+ {shippedDocs.length > 0 && (
+ <div>
+ <button
+ type="button"
+ onClick={() => setShippedOpen((v) => !v)}
+ className="w-full flex items-center gap-1.5 pl-14 pr-3 py-1 font-mono text-[11px] text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.06)]"
+ >
+ <Caret open={shippedOpen} />
+ <FolderIcon open={shippedOpen} />
+ <span>shipped/</span>
+ <span className="ml-auto text-[10px] text-[#556677]">
+ {shippedDocs.length}
+ </span>
+ </button>
+ {shippedOpen &&
+ shippedDocs.map((doc) => (
+ // Shipped docs render the doc row + its frozen
+ // tasks/ subfolder. The tasks/ folder defaults
+ // closed (history) so it doesn't dominate the
+ // sidebar; users can click to inspect what work
+ // produced this shipped contract.
+ <div key={doc.id}>
+ <DocumentRow
+ doc={doc}
+ directive={directive}
+ selected={doc.id === selectedDocumentId}
+ onSelect={() => onSelectDocument(directive.id, doc)}
+ indent="deep"
+ />
+ <DocumentTasksFolder
+ documentId={doc.id}
+ depth="deep"
+ defaultOpen={false}
+ refreshNonce={refreshNonce}
+ />
+ </div>
+ ))}
+ </div>
)}
- </li>
+ </>
)}
- </ul>
+ </div>
)}
</div>
);
}
-/**
- * Read-only viewer for a frozen contract revision. We render the markdown as
- * plain pre-formatted text — these are immutable historical records, not
- * places to edit. A header strip shows the PR state and a deep link.
- */
-function RevisionViewer({
- directiveId,
- revisionId,
-}: {
- directiveId: string;
- revisionId: string;
-}) {
- const [revision, setRevision] = useState<DirectiveRevision | null>(null);
- const [loading, setLoading] = useState(true);
+// =============================================================================
+// DocumentRow — one row inside a directive folder. The indent depth differs
+// between active rows (one level deep) and shipped rows (two levels deep).
+// =============================================================================
+
+interface DocumentRowProps {
+ doc: DirectiveDocument;
+ directive: DirectiveSummary;
+ selected: boolean;
+ onSelect: () => void;
+ indent?: "normal" | "deep";
+}
+
+function DocumentRow({
+ doc,
+ directive,
+ selected,
+ onSelect,
+ indent = "normal",
+}: DocumentRowProps) {
+ const dot = DOC_STATUS_DOT[doc.status] ?? DOC_STATUS_DOT.draft;
+ const padLeft = indent === "deep" ? "pl-[88px]" : "pl-14";
+ const name = `${fileLabel(doc, directive)}.md`;
+
+ return (
+ <button
+ type="button"
+ onClick={onSelect}
+ title={name}
+ className={`w-full text-left flex items-center gap-1.5 ${padLeft} pr-3 py-1 font-mono text-[11px] transition-colors ${
+ selected
+ ? "bg-[rgba(117,170,252,0.12)] text-white border-l-2 border-[#75aafc]"
+ : "text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.06)] border-l-2 border-transparent"
+ }`}
+ >
+ <FileIcon />
+ <span
+ className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${dot}`}
+ aria-hidden
+ title={doc.status}
+ />
+ <span className="truncate flex-1">{name}</span>
+ {/* Status chip — only shown for non-active states so the row stays
+ uncluttered for the common case. */}
+ {doc.status !== "active" && (
+ <span className="text-[9px] uppercase tracking-wide text-[#556677]">
+ {doc.status}
+ </span>
+ )}
+ {/* PR badge for shipped docs. The link short-circuits the row's
+ onClick so clicking the PR doesn't also re-select the doc. */}
+ {doc.prUrl && (doc.status === "shipped" || doc.status === "archived") && (
+ <a
+ href={doc.prUrl}
+ target="_blank"
+ rel="noreferrer noopener"
+ onClick={(e) => e.stopPropagation()}
+ className="text-[9px] text-[#75aafc] hover:text-white border border-[#2a3a5a] rounded px-1"
+ title={doc.prUrl}
+ >
+ PR
+ </a>
+ )}
+ </button>
+ );
+}
+
+// =============================================================================
+// Per-document tasks/ subfolder — fetches the steps + ephemeral tasks for a
+// single document and renders a collapsible `tasks/` row beneath the
+// document. Lazy: fetch only fires once the user opens the folder, and we
+// also keep the folder closed by default for shipped docs (where it's
+// historical) and open by default for active docs (where it's live work).
+// =============================================================================
+
+interface DocumentTasksFolderProps {
+ documentId: string;
+ /** Visual indent depth — mirrors the parent DocumentRow's indent so the
+ * tasks/ row sits one level deeper than its parent doc. */
+ depth: "normal" | "deep";
+ /** Whether to fetch+open by default. Active docs default to open so the
+ * user sees their live tasks immediately; shipped docs default to closed
+ * (historical), and the user can click to expand. */
+ defaultOpen: boolean;
+ /** Bumped externally so the folder refetches its task list after a save
+ * or status change elsewhere. Same nonce used for the directive folder. */
+ refreshNonce: number;
+}
+
+function DocumentTasksFolder({
+ documentId,
+ depth,
+ defaultOpen,
+ refreshNonce,
+}: DocumentTasksFolderProps) {
+ const [open, setOpen] = useState(defaultOpen);
+ const [data, setData] = useState<DocumentTasksResponse | null>(null);
+ const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
- useEffect(() => {
- let cancelled = false;
+ // Inner row indent is one level deeper than the folder header. Folder
+ // header uses pl-[88px] (deep) or pl-14 (normal); tasks rows go one
+ // step beyond that.
+ const headerPadLeft = depth === "deep" ? "pl-[88px]" : "pl-14";
+ const rowPadLeft = depth === "deep" ? "pl-[112px]" : "pl-[72px]";
+
+ const refresh = useCallback(async () => {
setLoading(true);
setError(null);
- listDirectiveRevisions(directiveId)
- .then((res) => {
- if (cancelled) return;
- const found = res.revisions.find((r) => r.id === revisionId) ?? null;
- if (!found) setError("Revision not found");
- setRevision(found);
- })
- .catch((err) => {
- if (cancelled) return;
- setError(err instanceof Error ? err.message : String(err));
- })
- .finally(() => {
- if (!cancelled) setLoading(false);
- });
- return () => {
- cancelled = true;
- };
- }, [directiveId, revisionId]);
+ try {
+ const res = await listDirectiveDocumentTasks(documentId);
+ setData(res);
+ } catch (e) {
+ setError(e instanceof Error ? e.message : "Failed to load tasks");
+ } finally {
+ setLoading(false);
+ }
+ }, [documentId]);
- if (loading) {
- return (
- <div className="flex-1 flex items-center justify-center">
- <p className="text-[#556677] font-mono text-[12px]">Loading revision…</p>
- </div>
- );
- }
- if (error || !revision) {
- return (
- <div className="flex-1 flex items-center justify-center">
- <p className="text-red-400 font-mono text-[12px]">
- {error ?? "Revision not found"}
- </p>
- </div>
- );
+ // Fetch when the folder is open (initial open or refresh). We don't
+ // pre-fetch on closed folders so we don't waste bandwidth on the long
+ // tail of historical shipped docs the user never expands.
+ useEffect(() => {
+ if (!open) return;
+ void refresh();
+ }, [open, refresh, refreshNonce]);
+
+ const total = (data?.steps.length ?? 0) + (data?.tasks.length ?? 0);
+
+ // Don't render the folder at all if we've fetched and the document has
+ // no tasks. This is the cleanest visual: a draft document just shows up
+ // as a single row with no children. The empty-folder check is gated on
+ // a successful fetch so we don't flash "no tasks/" rows during loading.
+ if (data && total === 0 && !loading && !error) {
+ return null;
}
return (
- <div className="flex-1 flex flex-col h-full overflow-hidden bg-[#0a1628]">
- <div className="flex-1 overflow-y-auto">
- <div className="max-w-3xl mx-auto px-8 py-10 text-[#dbe7ff]">
- <div className="flex items-center gap-3 mb-1">
- <h1 className="text-[24px] font-medium text-white tracking-tight">
- v{revision.version}
- </h1>
- <RevisionStateBadge prState={revision.prState} />
- </div>
- <p className="text-[10px] font-mono text-[#556677] uppercase tracking-wide mb-1">
- Frozen {new Date(revision.frozenAt).toLocaleString()}
- </p>
- <p className="text-[11px] font-mono text-[#7788aa] mb-8">
- <a
- href={revision.prUrl}
- target="_blank"
- rel="noreferrer"
- className="text-[#75aafc] hover:text-[#9bc3ff] underline"
- >
- {revision.prUrl}
- </a>
- </p>
-
- {/* Render the frozen markdown as plain pre-formatted text. We
- deliberately do not parse it into rich nodes — the goal is to
- show the historical record exactly as it was at PR time. */}
- <pre className="whitespace-pre-wrap break-words font-mono text-[13px] leading-relaxed text-[#e0eaf8]">
- {revision.content}
- </pre>
+ <div>
+ <button
+ type="button"
+ onClick={() => setOpen((v) => !v)}
+ className={`w-full flex items-center gap-1.5 ${headerPadLeft} pr-3 py-1 font-mono text-[11px] text-[#7788aa] hover:bg-[rgba(117,170,252,0.06)]`}
+ >
+ <Caret open={open} />
+ <FolderIcon open={open} />
+ <span>tasks/</span>
+ {total > 0 && (
+ <span className="ml-auto text-[10px] text-[#556677]">{total}</span>
+ )}
+ </button>
+ {open && (
+ <div className="py-0.5">
+ {loading && !data && (
+ <div className={`${rowPadLeft} pr-3 py-1 font-mono text-[10px] text-[#556677]`}>
+ Loading tasks…
+ </div>
+ )}
+ {error && (
+ <div className={`${rowPadLeft} pr-3 py-1 font-mono text-[10px] text-red-400`}>
+ {error}
+ </div>
+ )}
+ {data?.steps.map((step) => (
+ <StepRow key={`step-${step.id}`} step={step} padLeft={rowPadLeft} />
+ ))}
+ {data?.tasks.map((task) => (
+ <TaskRow key={`task-${task.id}`} task={task} padLeft={rowPadLeft} />
+ ))}
</div>
- </div>
+ )}
</div>
);
}
-/** Tiny pill showing the PR state of a revision (open / merged / closed). */
-function RevisionStateBadge({ prState }: { prState: string }) {
- const tone =
- prState === "merged"
- ? "text-emerald-300 border-emerald-700/60"
- : prState === "closed"
- ? "text-[#7788aa] border-[#2a3a5a]"
- : "text-amber-300 border-amber-600/40";
- return (
- <span
- className={`text-[9px] font-mono uppercase border rounded px-1 py-0 ${tone}`}
- >
- {prState}
- </span>
- );
+// Step status → coloured dot, mirroring directive status palette so the
+// sidebar reads consistently.
+const STEP_STATUS_DOT: Record<string, string> = {
+ pending: "bg-[#556677]",
+ ready: "bg-[#9bc3ff]",
+ running: "bg-yellow-400",
+ completed: "bg-green-400",
+ failed: "bg-red-400",
+ skipped: "bg-[#3a4a6a]",
+};
+
+// Task status → coloured dot. Statuses come from the Task model; the small
+// set we expect in directive context is enough — anything else falls back
+// to the muted "draft" colour.
+const TASK_STATUS_DOT: Record<string, string> = {
+ pending: "bg-[#556677]",
+ starting: "bg-yellow-400",
+ running: "bg-yellow-400",
+ completed: "bg-green-400",
+ failed: "bg-red-400",
+ cancelled: "bg-[#3a4a6a]",
+ interrupted: "bg-orange-400",
+};
+
+interface StepRowProps {
+ step: DirectiveStep;
+ padLeft: string;
}
-/**
- * Right-side status indicator. Composes the colored status dot with optional
- * "live" pulse (orchestrator running) and "glow" attention ring (pending user
- * question waiting on a response).
- */
-function StatusDot({
- color,
- live,
- glow,
- status,
-}: {
- color: string;
- live: boolean;
- glow: boolean;
- status: string;
-}) {
- // The glow is a soft amber ring pulsed via box-shadow. Keep it subtle so it
- // doesn't fight the live pulse for attention when both are present.
- const ring = glow
- ? "shadow-[0_0_0_2px_rgba(251,191,36,0.45),0_0_8px_2px_rgba(251,191,36,0.55)] animate-pulse"
- : "";
- const livePulse = live && !glow ? "animate-pulse" : "";
- const title = glow
- ? `${status} — needs response`
- : live
- ? `${status} — running`
- : `status: ${status}`;
+function StepRow({ step, padLeft }: StepRowProps) {
+ const dot = STEP_STATUS_DOT[step.status] ?? "bg-[#556677]";
return (
- <span
- className={`inline-block w-2 h-2 rounded-full shrink-0 ${color} ${ring} ${livePulse}`}
- aria-label={title}
- title={title}
- />
+ <div
+ title={`${step.name} (${step.status})`}
+ className={`w-full text-left flex items-center gap-1.5 ${padLeft} pr-3 py-1 font-mono text-[11px] text-[#9bc3ff]`}
+ >
+ <FileIcon />
+ <span
+ className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${dot}`}
+ aria-hidden
+ title={step.status}
+ />
+ <span className="truncate flex-1">{step.name}</span>
+ <span className="text-[9px] uppercase tracking-wide text-[#556677]">
+ step
+ </span>
+ </div>
);
}
-interface FolderTaskRow {
- taskId: string;
- /** Directive step id for step kinds — needed for complete/fail/skip APIs. */
- stepId: string | null;
- label: string;
- status: string;
- kind: "orchestrator-active" | "completion" | "step" | "ephemeral";
+interface TaskRowProps {
+ task: Task;
+ padLeft: string;
}
-function collectTasks(
- detailed: DirectiveWithSteps | null,
- summary: DirectiveSummary,
- ephemeralTasks: TaskSummary[],
-): FolderTaskRow[] {
- const rows: FolderTaskRow[] = [];
-
- // Orchestrator (planner) — surfaces only while it's actively running so
- // the folder is not flooded with stale orchestrator entries.
- const orchestratorId =
- detailed?.orchestratorTaskId ?? summary.orchestratorTaskId ?? null;
- if (orchestratorId) {
- rows.push({
- taskId: orchestratorId,
- stepId: null,
- label: "orchestrator",
- status: "running",
- kind: "orchestrator-active",
- });
- }
-
- // Completion (PR creation) task.
- const completionId =
- detailed?.completionTaskId ?? summary.completionTaskId ?? null;
- if (completionId) {
- rows.push({
- taskId: completionId,
- stepId: null,
- label: "completion",
- status: "running",
- kind: "completion",
- });
- }
-
- // Step tasks — only steps that have actually been started have a taskId.
- if (detailed) {
- for (const step of detailed.steps) {
- if (!step.taskId) continue;
- rows.push({
- taskId: step.taskId,
- stepId: step.id,
- label: step.name,
- status: step.status,
- kind: "step",
- });
- }
- }
-
- // Ephemeral tasks — user-spawned spinoffs not part of the DAG. Surfaced
- // alongside step tasks but with a different icon and the "ephemeral" kind
- // so context menus and the merge button behave correctly.
- for (const t of ephemeralTasks) {
- rows.push({
- taskId: t.id,
- stepId: null,
- label: t.name,
- status: t.status,
- kind: "ephemeral",
- });
- }
-
- return rows;
+function TaskRow({ task, padLeft }: TaskRowProps) {
+ const dot = TASK_STATUS_DOT[task.status] ?? "bg-[#556677]";
+ // Supervisor tasks get a small "sup" tag so the user can spot
+ // contract orchestrators in the list.
+ const isSup = task.isSupervisor;
+ return (
+ <div
+ title={`${task.name} (${task.status})`}
+ className={`w-full text-left flex items-center gap-1.5 ${padLeft} pr-3 py-1 font-mono text-[11px] text-[#9bc3ff]`}
+ >
+ <FileIcon />
+ <span
+ className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${dot}`}
+ aria-hidden
+ title={task.status}
+ />
+ <span className="truncate flex-1">{task.name}</span>
+ <span className="text-[9px] uppercase tracking-wide text-[#556677]">
+ {isSup ? "sup" : "task"}
+ </span>
+ </div>
+ );
}
+// =============================================================================
+// Sidebar
+// =============================================================================
+
interface SidebarProps {
directives: DirectiveSummary[];
loading: boolean;
selection: SidebarSelection | null;
- onSelect: (sel: SidebarSelection) => void;
- onDirectiveContextMenu: (e: React.MouseEvent, d: DirectiveSummary) => void;
- onTaskContextMenu: (e: React.MouseEvent, t: FolderTaskRow, directiveId: string) => void;
- /** Open the inline "+ New task" form for this directive. */
- onCreateTask: (d: DirectiveSummary) => void;
- /** Trigger a quick action (start/pause/PR) on the directive. */
- onQuickAction: (d: DirectiveSummary, action: "start" | "pause" | "pr") => void;
- /** Navigate to an orphan (no-directive) task's standalone view. */
- onSelectOrphan: (taskId: string) => void;
+ onSelectDocument: (directiveId: string, doc: DirectiveDocument) => void;
+ onSelectDirective: (directiveId: string) => void;
+ onCreateDocument: (directive: DirectiveSummary) => Promise<void>;
+ refreshNonce: number;
}
function DocumentSidebar({
directives,
loading,
selection,
- onSelect,
- onDirectiveContextMenu,
- onTaskContextMenu,
- onCreateTask,
- onQuickAction,
- onSelectOrphan,
+ onSelectDocument,
+ onSelectDirective,
+ onCreateDocument,
+ refreshNonce,
}: SidebarProps) {
- // Orphan tasks (no directive) — top-level "tmp/" pseudo-folder. Polled
- // every 5s so newly-spawned standalone tasks appear without a manual
- // refresh.
- const [orphanTasks, setOrphanTasks] = useState<TaskSummary[]>([]);
- useEffect(() => {
- let cancelled = false;
- const load = () => {
- listOrphanTasks()
- .then((res) => {
- if (!cancelled) setOrphanTasks(res.tasks);
- })
- .catch(() => {
- /* swallow — tmp/ is a nice-to-have, never blocking */
- });
+ const groups: Record<SidebarGroup, DirectiveSummary[]> = useMemo(() => {
+ const out: Record<SidebarGroup, DirectiveSummary[]> = {
+ active: [],
+ idle: [],
+ archived: [],
};
- load();
- const interval = setInterval(load, 5000);
- return () => {
- cancelled = true;
- clearInterval(interval);
- };
- }, []);
- const [tmpOpen, setTmpOpen] = useState<boolean>(true);
- // Pending user questions — drives the "glow" attention ring. We split into
- // two indices so the directive folder header glows whenever ANY of its
- // tasks has a pending question, while individual task rows glow only for
- // their own question.
- const { pendingQuestions } = useSupervisorQuestions();
- const { directivesWithPending, tasksWithPending } = useMemo(() => {
- const dirs = new Set<string>();
- const tasks = new Set<string>();
- for (const q of pendingQuestions) {
- if (q.directiveId) dirs.add(q.directiveId);
- if (q.taskId) tasks.add(q.taskId);
+ for (const d of directives) {
+ out[bucketOf(d.status)].push(d);
}
- return { directivesWithPending: dirs, tasksWithPending: tasks };
- }, [pendingQuestions]);
-
- // Sort active first, then idle, then paused, then drafts, then inactive
- // (shipped contracts are quieter), then archived.
- const sorted = useMemo(() => {
- const order: Record<DirectiveStatus, number> = {
- active: 0,
- paused: 1,
- idle: 2,
- draft: 3,
- inactive: 4,
- archived: 5,
- };
- return [...directives].sort((a, b) => {
- const oa = order[a.status] ?? 99;
- const ob = order[b.status] ?? 99;
- if (oa !== ob) return oa - ob;
- return a.title.localeCompare(b.title, undefined, { sensitivity: "base" });
+ // Sort each group alphabetically so it feels like a stable file tree.
+ (Object.keys(out) as SidebarGroup[]).forEach((k) => {
+ out[k].sort((a, b) =>
+ a.title.localeCompare(b.title, undefined, { sensitivity: "base" }),
+ );
});
+ return out;
}, [directives]);
- // Track which directive folders are open. The currently selected directive
- // is forced open so deep links land on something visible.
- const [openIds, setOpenIds] = useState<Set<string>>(new Set());
- const lastSelectedRef = useRef<string | null>(null);
+ // Default-collapsed state per group folder.
+ const [openGroups, setOpenGroups] = useState<Record<SidebarGroup, boolean>>({
+ active: true,
+ idle: true,
+ archived: false,
+ });
+
+ // Per-directive open state. We auto-open the directive containing the
+ // current selection so the user can see what they're editing.
+ const [openDirectives, setOpenDirectives] = useState<Record<string, boolean>>({});
+
useEffect(() => {
- if (selection && selection.directiveId !== lastSelectedRef.current) {
- lastSelectedRef.current = selection.directiveId;
- setOpenIds((prev) => {
- if (prev.has(selection.directiveId)) return prev;
- const next = new Set(prev);
- next.add(selection.directiveId);
- return next;
- });
- }
- }, [selection]);
-
- const toggleOpen = useCallback((id: string) => {
- setOpenIds((prev) => {
- const next = new Set(prev);
- if (next.has(id)) next.delete(id);
- else next.add(id);
- return next;
- });
- }, []);
+ if (!selection) return;
+ setOpenDirectives((prev) =>
+ prev[selection.directiveId] ? prev : { ...prev, [selection.directiveId]: true },
+ );
+ }, [selection?.directiveId]);
+
+ const toggleGroup = (g: SidebarGroup) =>
+ setOpenGroups((prev) => ({ ...prev, [g]: !prev[g] }));
+
+ const toggleDirective = (id: string) =>
+ setOpenDirectives((prev) => ({ ...prev, [id]: !prev[id] }));
return (
<div className="flex flex-col h-full">
{/* Sidebar header */}
<div className="flex items-center justify-between px-3 py-2 border-b border-dashed border-[rgba(117,170,252,0.2)]">
<span className="text-[11px] font-mono text-[#9bc3ff] uppercase tracking-wide">
- Contracts
+ Documents
</span>
<span className="text-[10px] font-mono text-[#556677]">
{directives.length}
</span>
</div>
- {/* Top-level "contracts/" folder header (informational, non-interactive). */}
+ {/* Top-level "directives/" folder */}
<div className="flex items-center gap-1.5 px-3 py-1.5 text-[11px] font-mono text-[#9bc3ff]">
<FolderIcon open />
- <span>contracts/</span>
+ <span>directives/</span>
</div>
{/* Body */}
<div className="flex-1 overflow-y-auto pb-4">
- {/* tmp/ pseudo-folder — orphan tasks (directive_id NULL). Always
- rendered so users can create scratchpad tasks even when zero
- directives exist; collapses to a thin header when empty. */}
- <div className="select-none border-b border-dashed border-[rgba(117,170,252,0.1)] pb-1 mb-1">
- <button
- type="button"
- onClick={() => setTmpOpen((p) => !p)}
- className="w-full flex items-center gap-1.5 pl-3 pr-3 py-1 font-mono text-[11px] text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.05)]"
- >
- <Caret open={tmpOpen} />
- <FolderIcon open={tmpOpen} />
- <span className="truncate flex-1 text-left text-[#7788aa]">tmp/</span>
- <span className="text-[10px] text-[#556677]">
- {orphanTasks.length}
- </span>
- </button>
- {tmpOpen && (
- <ul className="py-0.5">
- {orphanTasks.length === 0 ? (
- <li className="pl-8 pr-3 py-1 font-mono text-[10px] text-[#556677] italic">
- No orphan tasks
- </li>
- ) : (
- orphanTasks.map((t) => {
- const tdot =
- STEP_STATUS_DOT[t.status] ?? STEP_STATUS_DOT.pending;
- const live = t.status === "running";
- return (
- <li key={t.id}>
- <button
- type="button"
- onClick={() => onSelectOrphan(t.id)}
- title={t.name}
- className="w-full text-left flex items-center gap-1.5 pl-8 pr-3 py-1 font-mono text-[11px] text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.06)] border-l-2 border-transparent transition-colors"
- >
- <TaskIcon />
- <span className="truncate flex-1">{t.name}</span>
- <StatusDot
- color={tdot}
- live={live}
- glow={false}
- status={t.status}
- />
- </button>
- </li>
- );
- })
- )}
- </ul>
- )}
- </div>
-
{loading && directives.length === 0 ? (
<div className="px-3 py-6 text-center text-[#556677] font-mono text-[11px]">
Loading...
</div>
) : directives.length === 0 ? (
<div className="px-3 py-6 text-center text-[#556677] font-mono text-[11px]">
- No contracts yet
+ No directives yet
</div>
) : (
- sorted.map((d) => (
- <DirectiveFolder
- key={d.id}
- directive={d}
- open={openIds.has(d.id)}
- onToggle={() => toggleOpen(d.id)}
- selection={selection}
- onSelect={onSelect}
- pendingTaskIds={tasksWithPending}
- hasPendingForDirective={directivesWithPending.has(d.id)}
- onDirectiveContextMenu={onDirectiveContextMenu}
- onTaskContextMenu={onTaskContextMenu}
- onCreateTask={onCreateTask}
- onQuickAction={onQuickAction}
- />
- ))
+ (Object.keys(groups) as SidebarGroup[]).map((group) => {
+ const list = groups[group];
+ if (list.length === 0) return null;
+ const open = openGroups[group];
+ return (
+ <div key={group} className="select-none">
+ {/* Group header (sub-folder) */}
+ <button
+ type="button"
+ onClick={() => toggleGroup(group)}
+ className="w-full flex items-center gap-1.5 pl-4 pr-3 py-1 font-mono text-[11px] text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.05)]"
+ >
+ <Caret open={open} />
+ <FolderIcon open={open} />
+ <span>{GROUP_LABEL[group]}/</span>
+ <span className="ml-auto text-[10px] text-[#556677]">
+ {list.length}
+ </span>
+ </button>
+
+ {/* Each directive is a folder containing N documents. */}
+ {open && (
+ <div className="py-0.5">
+ {list.map((d) => (
+ <DirectiveFolder
+ key={d.id}
+ directive={d}
+ open={!!openDirectives[d.id]}
+ onToggle={() => toggleDirective(d.id)}
+ onHeaderClick={() => onSelectDirective(d.id)}
+ selection={selection}
+ onSelectDocument={onSelectDocument}
+ onCreateDocument={onCreateDocument}
+ refreshNonce={refreshNonce}
+ />
+ ))}
+ </div>
+ )}
+ </div>
+ );
+ })
)}
</div>
</div>
@@ -1166,108 +800,112 @@ function DocumentSidebar({
// =============================================================================
// Editor shell — wraps DocumentEditor and handles the "no document selected"
-// and loading states.
+// and loading states. Two modes:
+// 1) documentId selected → fetch the DirectiveDocument and edit doc.body via
+// updateDirectiveDocument (the call that auto-reactivates a shipped doc).
+// 2) no documentId (legacy fallback, kept for the "select a directive but
+// not a document" transitional case) → edit directive.goal as before.
// =============================================================================
-/**
- * Wraps DocumentTaskStream with ephemeral-aware metadata. Determines whether
- * the selected task is part of the directive's DAG (orchestrator/completion/
- * steps) or an ephemeral spinoff, and looks up its current status from the
- * ephemeral list — that decides whether the "Merge to base" affordance
- * should appear in the stream's action header.
- */
-function EphemeralAwareTaskStream({
- taskId,
- label,
- directive,
-}: {
- taskId: string;
- label: string;
- directive: DirectiveWithSteps;
-}) {
- const isStepBound =
- taskId === directive.orchestratorTaskId ||
- taskId === directive.completionTaskId ||
- directive.steps.some((s) => s.taskId === taskId);
-
- // Status lookup for ephemeral tasks. We poll the ephemeral list lazily —
- // this is a lightweight call and only triggers when the user is viewing a
- // task in the editor pane.
- const [ephemeralStatus, setEphemeralStatus] = useState<string | undefined>();
- useEffect(() => {
- if (isStepBound) return;
- let cancelled = false;
- const load = () => {
- listDirectiveEphemeralTasks(directive.id)
- .then((res) => {
- if (cancelled) return;
- const match = res.tasks.find((t) => t.id === taskId);
- setEphemeralStatus(match?.status);
- })
- .catch(() => {
- /* non-blocking */
- });
- };
- load();
- const interval = setInterval(load, 5000);
- return () => {
- cancelled = true;
- clearInterval(interval);
- };
- }, [taskId, directive.id, isStepBound]);
-
- return (
- <DocumentTaskStream
- taskId={taskId}
- label={label}
- ephemeral={!isStepBound}
- status={ephemeralStatus}
- />
- );
-}
-
interface EditorShellProps {
- selectedId: string | undefined;
- selectedTaskId: string | null;
+ selection: SidebarSelection | null;
hasDirectives: boolean;
listLoading: boolean;
- onClearTask: () => void;
+ /** Bumped after a successful document save so the sidebar refetches. */
+ onDocumentChanged: () => void;
}
function EditorShell({
- selectedId,
- selectedTaskId,
+ selection,
hasDirectives,
listLoading,
- onClearTask,
+ onDocumentChanged,
}: EditorShellProps) {
+ const directiveId = selection?.directiveId;
+ const documentId = selection?.documentId ?? null;
+
+ // We deliberately don't pull `updateGoal` here — in the multi-document
+ // world, edits flow through updateDirectiveDocument (which auto-reactivates
+ // a shipped doc when its body changes). The legacy directive.goal is
+ // unused on this surface.
const {
directive,
- loading,
- updateGoal,
+ loading: directiveLoading,
cleanup,
createPR,
pickUpOrders,
- } = useDirective(selectedId);
+ } = useDirective(directiveId);
+
+ // Document fetch — only when documentId is selected. Refetched whenever the
+ // id changes; not polled (the document stream is too low-traffic to warrant
+ // background refresh in this iteration).
+ const [doc, setDoc] = useState<DirectiveDocument | null>(null);
+ const [docLoading, setDocLoading] = useState(false);
+ const [docError, setDocError] = useState<string | null>(null);
+
+ useEffect(() => {
+ if (!documentId) {
+ setDoc(null);
+ setDocLoading(false);
+ setDocError(null);
+ return;
+ }
+ let cancelled = false;
+ setDocLoading(true);
+ setDocError(null);
+ getDirectiveDocument(documentId)
+ .then((d) => {
+ if (cancelled) return;
+ setDoc(d);
+ })
+ .catch((e) => {
+ if (cancelled) return;
+ setDocError(e instanceof Error ? e.message : "Failed to load document");
+ })
+ .finally(() => {
+ if (cancelled) return;
+ setDocLoading(false);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [documentId]);
+
+ // Save callback for the document path. The backend re-stamps a shipped doc
+ // back to active when its body changes, so we just optimistically update
+ // local state with the server's response.
+ const onUpdateDocumentBody = useCallback(
+ async (body: string) => {
+ if (!documentId) return;
+ const updated = await updateDirectiveDocument(documentId, { body });
+ setDoc(updated);
+ // Tell the sidebar to refetch the directive's document list so the
+ // status chip flips from `shipped` back to `active` (and any title
+ // changes propagate). Cheap — folders only refetch when open.
+ onDocumentChanged();
+ },
+ [documentId, onDocumentChanged],
+ );
- if (!selectedId) {
+ // ---- Empty / error / loading states ------------------------------------
+ if (!directiveId) {
return (
<div className="flex-1 flex items-center justify-center h-full">
<p className="text-[#556677] font-mono text-[12px]">
{listLoading
- ? "Loading contracts..."
+ ? "Loading documents..."
: hasDirectives
- ? "Select a contract from the sidebar"
- : "No contracts yet — create one from the legacy UI"}
+ ? "Select a document from the sidebar"
+ : "No directives yet — create one from the legacy UI"}
</p>
</div>
);
}
- if (loading && !directive) {
+ if (directiveLoading && !directive) {
return (
<div className="flex-1 flex items-center justify-center h-full">
- <p className="text-[#556677] font-mono text-[12px]">Loading contract...</p>
+ <p className="text-[#556677] font-mono text-[12px]">Loading directive...</p>
</div>
);
}
@@ -1275,108 +913,86 @@ function EditorShell({
if (!directive) {
return (
<div className="flex-1 flex items-center justify-center h-full">
- <p className="text-[#7788aa] font-mono text-[12px]">Contract not found</p>
+ <p className="text-[#7788aa] font-mono text-[12px]">Directive not found</p>
</div>
);
}
- // The "task" param can encode either a real task id, or a revision via the
- // `revision:<uuid>` prefix. Split that out so the right pane can switch
- // between the live task stream and the read-only revision viewer.
- const revisionId =
- selectedTaskId && selectedTaskId.startsWith("revision:")
- ? selectedTaskId.slice("revision:".length)
- : null;
- const realTaskId = revisionId ? null : selectedTaskId;
-
- // Resolve the label for the breadcrumb when a task is selected.
- const taskLabel = realTaskId
- ? realTaskId === directive.orchestratorTaskId
- ? "orchestrator"
- : realTaskId === directive.completionTaskId
- ? "completion"
- : directive.steps.find((s) => s.taskId === realTaskId)?.name ??
- realTaskId.slice(0, 8)
- : revisionId
- ? "revision"
- : null;
-
- // "Now executing" strip — surfaces what's live when looking at the
- // contract editor, so users don't have to scan the sidebar to find it.
- const liveTask = (() => {
- if (selectedTaskId) return null; // already viewing a task; strip is redundant
- if (directive.orchestratorTaskId) {
- return { id: directive.orchestratorTaskId, name: "orchestrator" };
+ // --- Document path: documentId selected --------------------------------
+ if (documentId) {
+ if (docLoading && !doc) {
+ return (
+ <div className="flex-1 flex items-center justify-center h-full">
+ <p className="text-[#556677] font-mono text-[12px]">Loading document...</p>
+ </div>
+ );
}
- if (directive.completionTaskId) {
- return { id: directive.completionTaskId, name: "completion" };
+ if (docError) {
+ return (
+ <div className="flex-1 flex items-center justify-center h-full">
+ <p className="text-red-400 font-mono text-[12px]">{docError}</p>
+ </div>
+ );
}
- const runningStep = directive.steps.find((s) => s.status === "running");
- if (runningStep && runningStep.taskId) {
- return { id: runningStep.taskId, name: runningStep.name };
+ if (!doc) {
+ return (
+ <div className="flex-1 flex items-center justify-center h-full">
+ <p className="text-[#7788aa] font-mono text-[12px]">Document not found</p>
+ </div>
+ );
}
- return null;
- })();
- return (
- <div className="flex-1 flex flex-col h-full overflow-hidden">
- {/* Contract header — breadcrumb-like, mirrors a code editor's tab bar */}
- <div className="px-6 py-3 border-b border-dashed border-[rgba(117,170,252,0.2)]">
- <div className="flex items-center gap-2 text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">
- <FileIcon />
- <span>contracts /</span>
- <span className="text-[#9bc3ff]">{directive.id.slice(0, 8)}</span>
- {selectedTaskId && (
- <>
- <span>/</span>
- <span className="text-[#9bc3ff]">{taskLabel}</span>
- <button
- type="button"
- onClick={onClearTask}
- className="ml-2 px-1.5 py-0.5 text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded normal-case"
- >
- back to contract
- </button>
- </>
- )}
- </div>
- </div>
+ // Synthesise a directive-shaped object whose `goal` is the document body.
+ // DocumentEditor was originally written against DirectiveWithSteps, so we
+ // can keep its shape by overriding `goal` with `doc.body` and `title`
+ // with the document's filename label. The steps panel still draws from
+ // the real directive (passed through StepsBlockContextProvider).
+ const docTitle = `${fileLabel(doc, directive)}.md`;
+ const directiveAsDocument = {
+ ...directive,
+ goal: doc.body,
+ title: docTitle,
+ };
- {/* Now-executing strip — only when viewing the contract doc itself.
- Click to jump into the live task transcript. */}
- {liveTask && (
- <button
- type="button"
- onClick={() =>
- // Navigate via the search-param so EditorShell switches to the
- // task stream for this live task.
- (window.location.search = `?task=${liveTask.id}`)
- }
- className="shrink-0 flex items-center gap-2 px-6 py-1.5 bg-amber-900/15 border-b border-amber-700/40 text-amber-300 font-mono text-[11px] hover:bg-amber-900/30 transition-colors"
- >
- <span className="inline-block w-1.5 h-1.5 rounded-full bg-amber-400 animate-pulse" />
- <span className="uppercase tracking-wide text-[10px]">Now executing</span>
- <span className="text-[#dbe7ff]">{liveTask.name}</span>
- <span className="ml-auto text-[10px] text-amber-200/70">
- click to view transcript ↗
- </span>
- </button>
- )}
+ return (
+ <div className="flex-1 flex flex-col h-full overflow-hidden">
+ {/* Breadcrumb — directives / <directive title> / <document title>.md */}
+ <div className="px-6 py-3 border-b border-dashed border-[rgba(117,170,252,0.2)]">
+ <div className="flex items-center gap-2 text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">
+ <FileIcon />
+ <span>directives /</span>
+ <span className="text-[#9bc3ff]">
+ {directive.title.trim().length > 0
+ ? directive.title
+ : directive.id.slice(0, 8)}
+ </span>
+ <span>/</span>
+ <span className="text-white">{docTitle}</span>
+ {doc.status === "shipped" && (
+ <span className="ml-2 text-[#75aafc] normal-case">shipped</span>
+ )}
+ {doc.status === "archived" && (
+ <span className="ml-2 text-[#7788aa] normal-case">archived</span>
+ )}
+ {doc.status === "draft" && (
+ <span className="ml-2 text-[#556677] normal-case">draft</span>
+ )}
+ {!!directive.orchestratorTaskId && (
+ <span className="ml-auto inline-flex items-center gap-1 text-yellow-400">
+ <span className="inline-block w-1.5 h-1.5 rounded-full bg-yellow-400 animate-pulse" />
+ orchestrator running
+ </span>
+ )}
+ </div>
+ </div>
- {revisionId ? (
- <RevisionViewer directiveId={directive.id} revisionId={revisionId} />
- ) : realTaskId ? (
- <EphemeralAwareTaskStream
- taskId={realTaskId}
- label={taskLabel ?? realTaskId.slice(0, 8)}
- directive={directive}
- />
- ) : (
<DocumentEditor
- directive={directive}
- onUpdateGoal={async (goal) => {
- await updateGoal(goal);
- }}
+ // Keying by document id ensures the Lexical editor remounts cleanly
+ // when the user switches documents, so the previous doc's body
+ // doesn't bleed into the new one.
+ key={doc.id}
+ directive={directiveAsDocument}
+ onUpdateGoal={onUpdateDocumentBody}
onCleanup={async () => {
await cleanup();
}}
@@ -1387,7 +1003,33 @@ function EditorShell({
await pickUpOrders();
}}
/>
- )}
+ </div>
+ );
+ }
+
+ // --- Legacy fallback: directive selected but no document chosen --------
+ // We only ever land here transiently while the page resolves the default
+ // document selection, so we render a thin "loading" placeholder rather
+ // than the full goal editor (which would be confusing alongside the new
+ // multi-document model).
+ return (
+ <div className="flex-1 flex flex-col h-full overflow-hidden">
+ <div className="px-6 py-3 border-b border-dashed border-[rgba(117,170,252,0.2)]">
+ <div className="flex items-center gap-2 text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">
+ <FileIcon />
+ <span>directives /</span>
+ <span className="text-[#9bc3ff]">
+ {directive.title.trim().length > 0
+ ? directive.title
+ : directive.id.slice(0, 8)}
+ </span>
+ </div>
+ </div>
+ <div className="flex-1 flex items-center justify-center">
+ <p className="text-[#556677] font-mono text-[12px]">
+ Select a document, or click "+ New document" to create one.
+ </p>
+ </div>
</div>
);
}
@@ -1396,25 +1038,31 @@ function EditorShell({
// Page
// =============================================================================
-type ContextMenuState =
- | { kind: "directive"; x: number; y: number; directive: DirectiveSummary }
- | {
- kind: "task";
- x: number;
- y: number;
- task: FolderTaskRow;
- directiveId: string;
- }
- | null;
-
export default function DocumentDirectivesPage() {
const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth();
const navigate = useNavigate();
- const { id: selectedId } = useParams<{ id: string }>();
+ const { id: routeDirectiveId } = useParams<{ id: string }>();
const [searchParams, setSearchParams] = useSearchParams();
- const selectedTaskId = searchParams.get("task");
- const { directives, loading: listLoading, refresh: refreshList } = useDirectives();
- const [contextMenu, setContextMenu] = useState<ContextMenuState>(null);
+ const { directives, loading: listLoading } = useDirectives();
+
+ // refreshNonce — bumped to tell open directive folders to refetch their
+ // document lists (after a create or save).
+ const [refreshNonce, setRefreshNonce] = useState(0);
+ const bumpRefresh = useCallback(() => setRefreshNonce((n) => n + 1), []);
+
+ // Derive the SidebarSelection from the URL. The route param is the
+ // directive id; ?document=:id and ?task=:id pick a specific child. Exactly
+ // one of taskId/documentId can be set; if both happen to be present in the
+ // URL (which shouldn't happen via our nav code) we prefer ?task= since
+ // task selection is the more disruptive action.
+ const selection: SidebarSelection | null = useMemo(() => {
+ if (!routeDirectiveId) return null;
+ const taskId = searchParams.get("task");
+ const documentId = searchParams.get("document");
+ if (taskId) return { directiveId: routeDirectiveId, taskId, documentId: null };
+ if (documentId) return { directiveId: routeDirectiveId, taskId: null, documentId };
+ return { directiveId: routeDirectiveId, taskId: null, documentId: null };
+ }, [routeDirectiveId, searchParams]);
useEffect(() => {
if (!authLoading && isAuthConfigured && !isAuthenticated) {
@@ -1422,96 +1070,86 @@ export default function DocumentDirectivesPage() {
}
}, [authLoading, isAuthConfigured, isAuthenticated, navigate]);
- const onSelect = useCallback(
- (sel: SidebarSelection) => {
- const next = `/directives/${sel.directiveId}${
- sel.taskId ? `?task=${sel.taskId}` : ""
- }`;
- navigate(next);
- },
- [navigate],
- );
+ // ------------------------------------------------------------------
+ // Default-selection: when the user clicks a directive's folder header (or
+ // lands on /directives/:id without ?document=) we pick the first active or
+ // draft document and update the URL to point at it. This avoids the
+ // "directive selected, but nothing in the editor" intermediate state.
+ // ------------------------------------------------------------------
+ const lastResolvedRef = useRef<string | null>(null);
+ useEffect(() => {
+ if (!routeDirectiveId) {
+ lastResolvedRef.current = null;
+ return;
+ }
+ // Only auto-resolve when no document/task has been picked yet, AND we
+ // haven't already resolved this directive in a prior tick (otherwise
+ // navigating away from the doc would instantly re-pick the same one).
+ if (selection?.documentId || selection?.taskId) {
+ lastResolvedRef.current = routeDirectiveId;
+ return;
+ }
+ if (lastResolvedRef.current === routeDirectiveId) return;
+ lastResolvedRef.current = routeDirectiveId;
- const onClearTask = useCallback(() => {
- const next = new URLSearchParams(searchParams);
- next.delete("task");
- setSearchParams(next, { replace: true });
- }, [searchParams, setSearchParams]);
-
- const onDirectiveContextMenu = useCallback(
- (e: React.MouseEvent, d: DirectiveSummary) => {
- e.preventDefault();
- e.stopPropagation();
- setContextMenu({ kind: "directive", x: e.clientX, y: e.clientY, directive: d });
- },
- [],
- );
+ let cancelled = false;
+ listDirectiveDocuments(routeDirectiveId)
+ .then((list) => {
+ if (cancelled) return;
+ // Prefer the first 'active' doc; fall back to the first 'draft'.
+ const firstActive = list.find((d) => d.status === "active");
+ const firstDraft = list.find((d) => d.status === "draft");
+ const pick = firstActive ?? firstDraft;
+ if (pick) {
+ setSearchParams(
+ (prev) => {
+ const next = new URLSearchParams(prev);
+ next.set("document", pick.id);
+ next.delete("task");
+ return next;
+ },
+ { replace: true },
+ );
+ }
+ })
+ .catch(() => {
+ // Swallow — the editor pane will show "Document not found" and the
+ // user can click "+ New document" to recover.
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [routeDirectiveId, selection?.documentId, selection?.taskId, setSearchParams]);
- const onTaskContextMenu = useCallback(
- (e: React.MouseEvent, task: FolderTaskRow, directiveId: string) => {
- e.preventDefault();
- e.stopPropagation();
- setContextMenu({ kind: "task", x: e.clientX, y: e.clientY, task, directiveId });
+ const handleSelectDocument = useCallback(
+ (directiveId: string, doc: DirectiveDocument) => {
+ navigate(`/directives/${directiveId}?document=${doc.id}`);
},
- [],
- );
-
- const closeContextMenu = useCallback(() => setContextMenu(null), []);
-
- // Inline "+ New task" form state. When set, we render a small modal-ish
- // overlay anchored to the directive folder; submitting calls the
- // ephemeral-task endpoint.
- const [newTaskFor, setNewTaskFor] = useState<DirectiveSummary | null>(null);
-
- const onCreateTask = useCallback((d: DirectiveSummary) => {
- setNewTaskFor(d);
- }, []);
-
- const handleSubmitNewTask = useCallback(
- async (name: string, plan: string) => {
- if (!newTaskFor) return;
- try {
- const task = await createDirectiveTask(newTaskFor.id, { name, plan });
- // Navigate the user into the freshly-spawned task's transcript.
- navigate(`/directives/${newTaskFor.id}?task=${task.id}`);
- setNewTaskFor(null);
- } catch (err) {
- // eslint-disable-next-line no-console
- console.error("[makima] failed to create ephemeral task", err);
- alert(
- err instanceof Error
- ? `Failed to create task: ${err.message}`
- : "Failed to create task",
- );
- }
- },
- [newTaskFor, navigate],
+ [navigate],
);
- const onQuickAction = useCallback(
- async (d: DirectiveSummary, action: "start" | "pause" | "pr") => {
- try {
- if (action === "start") {
- await startDirective(d.id);
- } else if (action === "pause") {
- await pauseDirective(d.id);
- } else if (action === "pr") {
- await createDirectivePR(d.id);
- }
- await refreshList();
- } catch (err) {
- // eslint-disable-next-line no-console
- console.error(`[makima] quick action ${action} failed`, err);
- }
+ // When the user clicks a directive folder header (not a document row), we
+ // jump to /directives/:id without ?document= — the default-selection
+ // effect above will then pick the first active doc.
+ const handleSelectDirective = useCallback(
+ (directiveId: string) => {
+ if (routeDirectiveId === directiveId) return;
+ navigate(`/directives/${directiveId}`);
},
- [refreshList],
+ [navigate, routeDirectiveId],
);
- const onSelectOrphan = useCallback(
- (taskId: string) => {
- navigate(`/tmp/${taskId}`);
+ const handleCreateDocument = useCallback(
+ async (directive: DirectiveSummary) => {
+ const created = await createDirectiveDocument(directive.id, {
+ title: "",
+ body: "",
+ });
+ bumpRefresh();
+ // Navigate to the new doc so it's selected immediately.
+ navigate(`/directives/${directive.id}?document=${created.id}`);
},
- [navigate],
+ [bumpRefresh, navigate],
);
if (authLoading) {
@@ -1525,10 +1163,6 @@ export default function DocumentDirectivesPage() {
);
}
- const selection: SidebarSelection | null = selectedId
- ? { directiveId: selectedId, taskId: selectedTaskId }
- : null;
-
return (
// h-screen + overflow-hidden so the page itself never scrolls; the
// sidebar and editor pane each manage their own scroll via flex-1
@@ -1546,269 +1180,21 @@ export default function DocumentDirectivesPage() {
directives={directives}
loading={listLoading}
selection={selection}
- onSelect={onSelect}
- onDirectiveContextMenu={onDirectiveContextMenu}
- onTaskContextMenu={onTaskContextMenu}
- onCreateTask={onCreateTask}
- onQuickAction={onQuickAction}
- onSelectOrphan={onSelectOrphan}
+ onSelectDocument={handleSelectDocument}
+ onSelectDirective={handleSelectDirective}
+ onCreateDocument={handleCreateDocument}
+ refreshNonce={refreshNonce}
/>
</div>
- {/* Right: Lexical editor / task stream */}
+ {/* Right: Lexical editor */}
<EditorShell
- selectedId={selectedId}
- selectedTaskId={selectedTaskId}
+ selection={selection}
hasDirectives={directives.length > 0}
listLoading={listLoading}
- onClearTask={onClearTask}
+ onDocumentChanged={bumpRefresh}
/>
</main>
-
- {/* Context menus — rendered at page level so they overlay everything. */}
- {contextMenu?.kind === "directive" && (
- <DirectiveContextMenu
- x={contextMenu.x}
- y={contextMenu.y}
- directive={contextMenu.directive}
- onClose={closeContextMenu}
- onStart={async () => {
- await startDirective(contextMenu.directive.id);
- await refreshList();
- }}
- onPause={async () => {
- await pauseDirective(contextMenu.directive.id);
- await refreshList();
- }}
- onArchive={async () => {
- await updateDirective(contextMenu.directive.id, {
- status: "archived",
- });
- await refreshList();
- }}
- onDelete={async () => {
- if (
- !window.confirm(
- `Delete "${contextMenu.directive.title}"? This cannot be undone.`,
- )
- ) {
- return;
- }
- await deleteDirective(contextMenu.directive.id);
- await refreshList();
- // If the deleted one was selected, clear selection.
- if (selectedId === contextMenu.directive.id) {
- navigate("/directives");
- }
- }}
- onGoToPR={() => {
- if (contextMenu.directive.prUrl) {
- window.open(contextMenu.directive.prUrl, "_blank", "noreferrer");
- }
- }}
- onNewDraft={async () => {
- await newDirectiveDraft(contextMenu.directive.id);
- await refreshList();
- // Send the user into the freshly-cleared contract so they can
- // start typing the next iteration immediately.
- navigate(`/directives/${contextMenu.directive.id}`);
- }}
- onCreatePR={async () => {
- await createDirectivePR(contextMenu.directive.id);
- await refreshList();
- }}
- onAdvance={async () => {
- await advanceDirective(contextMenu.directive.id);
- await refreshList();
- }}
- onCleanup={async () => {
- await cleanupDirective(contextMenu.directive.id);
- await refreshList();
- }}
- onPickUpOrders={async () => {
- await pickUpOrders(contextMenu.directive.id);
- await refreshList();
- }}
- />
- )}
- {contextMenu?.kind === "task" && (
- <TaskContextMenu
- x={contextMenu.x}
- y={contextMenu.y}
- task={contextMenu.task}
- onClose={closeContextMenu}
- onInterrupt={async () => {
- try {
- await stopTask(contextMenu.task.taskId);
- } catch (err) {
- // eslint-disable-next-line no-console
- console.error("[makima] failed to interrupt task", err);
- }
- await refreshList();
- }}
- onComplete={async () => {
- if (!contextMenu.task.stepId) return;
- await completeDirectiveStep(
- contextMenu.directiveId,
- contextMenu.task.stepId,
- );
- await refreshList();
- }}
- onFail={async () => {
- if (!contextMenu.task.stepId) return;
- await failDirectiveStep(
- contextMenu.directiveId,
- contextMenu.task.stepId,
- );
- await refreshList();
- }}
- onSkip={async () => {
- if (!contextMenu.task.stepId) return;
- await skipDirectiveStep(
- contextMenu.directiveId,
- contextMenu.task.stepId,
- );
- await refreshList();
- }}
- onSendMessage={async () => {
- // Browser prompt is the lightest-weight surface that doesn't
- // require redesigning a modal. The same comment box is also
- // available below the live transcript when the task is selected.
- const message = window.prompt("Send message to task:");
- if (!message || !message.trim()) return;
- try {
- await sendTaskMessage(contextMenu.task.taskId, message.trim());
- } catch (err) {
- // eslint-disable-next-line no-console
- console.error("[makima] failed to send task message", err);
- }
- }}
- onOpenInTaskPage={() => {
- // The standalone /exec/:taskId page has the full task UI with
- // worktree diff viewer, checkpoint controls, etc.
- navigate(`/exec/${contextMenu.task.taskId}`);
- }}
- />
- )}
-
- {newTaskFor && (
- <NewTaskModal
- directive={newTaskFor}
- onClose={() => setNewTaskFor(null)}
- onSubmit={handleSubmitNewTask}
- />
- )}
- </div>
- );
-}
-
-/**
- * Inline "+ New task" form for spawning an ephemeral task under a
- * directive. Surfaced as a centered modal, dismissible with Esc / click-out.
- */
-function NewTaskModal({
- directive,
- onClose,
- onSubmit,
-}: {
- directive: DirectiveSummary;
- onClose: () => void;
- onSubmit: (name: string, plan: string) => Promise<void>;
-}) {
- const [name, setName] = useState("");
- const [plan, setPlan] = useState("");
- const [submitting, setSubmitting] = useState(false);
- const nameRef = useRef<HTMLInputElement>(null);
-
- useEffect(() => {
- nameRef.current?.focus();
- const onKey = (e: KeyboardEvent) => {
- if (e.key === "Escape") onClose();
- };
- document.addEventListener("keydown", onKey);
- return () => document.removeEventListener("keydown", onKey);
- }, [onClose]);
-
- const submit = async (e: React.FormEvent) => {
- e.preventDefault();
- const trimmedName = name.trim();
- const trimmedPlan = plan.trim();
- if (!trimmedName || !trimmedPlan || submitting) return;
- setSubmitting(true);
- try {
- await onSubmit(trimmedName, trimmedPlan);
- } finally {
- setSubmitting(false);
- }
- };
-
- return (
- <div
- className="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
- onClick={onClose}
- >
- <form
- onSubmit={submit}
- onClick={(e) => e.stopPropagation()}
- className="w-[480px] max-w-[90vw] bg-[#0a1628] border border-[rgba(117,170,252,0.4)] shadow-xl flex flex-col"
- >
- <div className="px-4 py-3 border-b border-dashed border-[rgba(117,170,252,0.25)]">
- <p className="text-[10px] font-mono text-[#556677] uppercase tracking-wide">
- New task in
- </p>
- <p className="text-[12px] font-mono text-white truncate">
- {directive.title}
- </p>
- </div>
- <div className="px-4 py-4 space-y-3">
- <div className="space-y-1">
- <label className="text-[10px] font-mono text-[#7788aa] uppercase tracking-wide">
- Name
- </label>
- <input
- ref={nameRef}
- type="text"
- value={name}
- onChange={(e) => setName(e.target.value)}
- placeholder="e.g. Investigate flaky test in auth.test.ts"
- className="w-full bg-transparent border border-[rgba(117,170,252,0.2)] focus:border-[#75aafc] outline-none px-3 py-2 text-[13px] text-[#dbe7ff] placeholder-[#445566]"
- />
- </div>
- <div className="space-y-1">
- <label className="text-[10px] font-mono text-[#7788aa] uppercase tracking-wide">
- Plan / instructions
- </label>
- <textarea
- value={plan}
- onChange={(e) => setPlan(e.target.value)}
- onKeyDown={(e) => {
- if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
- void submit(e as unknown as React.FormEvent);
- }
- }}
- rows={5}
- placeholder="What should the task do?"
- className="w-full bg-transparent border border-[rgba(117,170,252,0.2)] focus:border-[#75aafc] outline-none px-3 py-2 text-[13px] text-[#dbe7ff] placeholder-[#445566] resize-none"
- />
- </div>
- </div>
- <div className="px-4 py-3 border-t border-dashed border-[rgba(117,170,252,0.25)] flex items-center justify-end gap-2">
- <button
- type="button"
- onClick={onClose}
- className="px-3 py-1.5 text-[11px] font-mono uppercase tracking-wide text-[#7788aa] border border-[#2a3a5a] hover:text-white"
- >
- Cancel
- </button>
- <button
- type="submit"
- disabled={!name.trim() || !plan.trim() || submitting}
- className="px-3 py-1.5 text-[11px] font-mono uppercase tracking-wide text-emerald-300 border border-emerald-700/60 hover:border-emerald-400 disabled:opacity-40 disabled:cursor-not-allowed"
- >
- {submitting ? "Creating…" : "Spawn task"}
- </button>
- </div>
- </form>
</div>
);
}
diff --git a/makima/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo
index 56c723a..a520296 100644
--- a/makima/frontend/tsconfig.tsbuildinfo
+++ b/makima/frontend/tsconfig.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/commandmodepanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/directives/doglist.tsx","./src/components/directives/directivecontextmenu.tsx","./src/components/directives/directivedag.tsx","./src/components/directives/directivedetail.tsx","./src/components/directives/directivelist.tsx","./src/components/directives/directivelogstream.tsx","./src/components/directives/documenteditor.tsx","./src/components/directives/orchestratorstepnode.tsx","./src/components/directives/stepnode.tsx","./src/components/directives/stepsblocknode.tsx","./src/components/directives/taskslideoutpanel.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/discusscontractmodal.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/orders/ordercontextmenu.tsx","./src/components/orders/orderdetail.tsx","./src/components/orders/orderlist.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usedirectives.ts","./src/hooks/usedogs.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usemultitasksubscription.ts","./src/hooks/useorders.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useusersettings.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/daemons.tsx","./src/routes/directives.tsx","./src/routes/document-directives.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/orders.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/types/messages.ts"],"version":"5.9.3"} \ No newline at end of file
+{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/quickswitcher.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/commandmodepanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/directives/doglist.tsx","./src/components/directives/directivecontextmenu.tsx","./src/components/directives/directivedag.tsx","./src/components/directives/directivedetail.tsx","./src/components/directives/directivelist.tsx","./src/components/directives/directivelogstream.tsx","./src/components/directives/documenteditor.tsx","./src/components/directives/documenttaskstream.tsx","./src/components/directives/orchestratorstepnode.tsx","./src/components/directives/stepnode.tsx","./src/components/directives/stepsblocknode.tsx","./src/components/directives/taskslideoutpanel.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/discusscontractmodal.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/orders/ordercontextmenu.tsx","./src/components/orders/orderdetail.tsx","./src/components/orders/orderlist.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usedirectives.ts","./src/hooks/usedogs.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usemultitasksubscription.ts","./src/hooks/useorders.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useusersettings.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/daemons.tsx","./src/routes/directives.tsx","./src/routes/document-directives.tsx","./src/routes/exec-redirect.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/orders.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/routes/tmp.tsx","./src/types/messages.ts"],"version":"5.9.3"} \ No newline at end of file