oh-my-llmwikimode 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/design/EOS_CHAT_HOME_IA.md +49 -10
- package/docs/design/EOS_CHAT_OPERATING_LOOP.md +30 -3
- package/docs/design/EOS_DESIGN.md +41 -0
- package/docs/design/EOS_SIDE_REVIEW_IA.md +22 -1
- package/docs/design/EOS_VISUAL_SCREENSHOT_HARNESS.md +4 -3
- package/docs/eos-workspace-boundary-mvp.md +33 -2
- package/package.json +3 -1
- package/src/eos/chat/static/styles.css +1 -1
- package/src/eos/home.js +136 -1
- package/src/eos/http/app.js +16 -3
- package/src/eos/side-review/static/styles.css +4 -4
- package/src/eos/visual-qa/accessibility.js +7 -0
- package/src/eos/visual-qa/screenshot-harness.js +7 -0
- package/src/eos/workspace/static/app.js +159 -0
- package/src/eos/workspace/static/index.html +95 -0
- package/src/eos/workspace/static/styles.css +425 -0
- package/src/eos/workspace-boundary/side-review-workflow.js +3 -0
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
# Eos Chat Home Information Architecture
|
|
2
2
|
|
|
3
|
-
**Status:** Active implementation contract for Eos Chat Command Center MVP
|
|
3
|
+
**Status:** Active implementation contract for EOS Workspace and Eos Chat Command Center MVP
|
|
4
4
|
**Date:** 2026-05-29
|
|
5
5
|
**Depends on:** `EOS_DESIGN.md`, `EOS_UI_CONTRACT.md`, `EOS_SIDE_REVIEW_IA.md`
|
|
6
6
|
|
|
7
7
|
## 1. Purpose
|
|
8
8
|
|
|
9
|
-
`
|
|
9
|
+
`EOS Workspace` at `/workspace/` is the browser-first Home for Eos. `Eos Chat` remains the direct chat capture surface and compatibility route.
|
|
10
10
|
|
|
11
|
-
The user starts
|
|
11
|
+
The user starts with Chat-first capture: saying what they want so Eos captures that intent as a local review-required artifact, then makes it visible in `Side Review`. Chat does not execute shell commands, providers, agents, wiki promotion, git actions, or background work by itself.
|
|
12
|
+
|
|
13
|
+
EOS Workspace uses the **Local AI Operating Workbench** shell:
|
|
14
|
+
|
|
15
|
+
- left app rail for Home, Chat, Side Review, and future evidence/status surfaces.
|
|
16
|
+
- top workspace tabs for keeping multiple work surfaces open while the user compares context.
|
|
17
|
+
- main work area for the active capture, review, or evidence surface.
|
|
18
|
+
- right inspector for artifact metadata, source paths, evidence, and prepared decision details.
|
|
19
|
+
- bottom status strip for Local Only, Review Required, Not Applied, daemon/token state, and harness readiness.
|
|
20
|
+
|
|
21
|
+
The shell is web-service first. It is not a VS Code extension app, and it does not require a VSIX runtime.
|
|
12
22
|
|
|
13
23
|
## 2. Primary promise
|
|
14
24
|
|
|
@@ -20,12 +30,16 @@ Tell Eos what you want to prepare. Eos captures it for local review before anyth
|
|
|
20
30
|
|
|
21
31
|
## 3. Required regions
|
|
22
32
|
|
|
23
|
-
1. **
|
|
24
|
-
2. **
|
|
25
|
-
3. **
|
|
26
|
-
4. **
|
|
27
|
-
5. **
|
|
28
|
-
6. **
|
|
33
|
+
1. **Left app rail** - persistent navigation for `EOS Workspace`, `Eos Chat`, and `Side Review`.
|
|
34
|
+
2. **Top workspace tabs** - browser-like open surfaces for Home, Chat, Side Review, and future evidence panels.
|
|
35
|
+
3. **Header rail** - `EOS Workspace`, short local workspace status, and links to `Eos Chat` and `Side Review`.
|
|
36
|
+
4. **Safety strip** - always-visible copy: `Local review workspace. Nothing is applied automatically.`
|
|
37
|
+
5. **Welcome state** - explains chat-first workflow without starter chips.
|
|
38
|
+
6. **Status cards** - wiki status, review queue count, and harness readiness in compact cards.
|
|
39
|
+
7. **Main work area** - local acknowledgements and the active task surface only; no execution transcript unless a future approved execution flow exists.
|
|
40
|
+
8. **Right inspector** - optional artifact, evidence, and prepared decision detail surface.
|
|
41
|
+
9. **Composer** - bottom input with a short placeholder and explicit send button.
|
|
42
|
+
10. **Bottom status strip** - local-only, review-required, Not Applied, and route/auth status.
|
|
29
43
|
|
|
30
44
|
## 4. Empty state copy
|
|
31
45
|
|
|
@@ -46,6 +60,7 @@ Do not add starter chips in this slice. Shortcuts may return later only after re
|
|
|
46
60
|
- Button label: `Capture for Review`
|
|
47
61
|
- Success acknowledgement: `Captured for review.`
|
|
48
62
|
- Follow-up link/action: `Open Side Review`
|
|
63
|
+
- Compatibility link/action: `Open Chat`
|
|
49
64
|
|
|
50
65
|
The composer must not use copy such as `Run`, `Execute`, `Complete`, `Apply`, or `Push`.
|
|
51
66
|
|
|
@@ -82,16 +97,25 @@ Do not use these labels in the Eos Chat MVP:
|
|
|
82
97
|
|
|
83
98
|
- `GET /chat/` serves the Eos Chat local UI.
|
|
84
99
|
- `GET /chat` redirects to `/chat/`.
|
|
100
|
+
- `GET /workspace/` serves the canonical operating workbench and EOS Workspace Home.
|
|
101
|
+
- `GET /workspace` redirects to `/workspace/`.
|
|
102
|
+
- `GET /side-review/` serves the review desk for local artifacts, evidence, and prepare-only decision notes.
|
|
85
103
|
- `GET /api/home` remains a read-only home data snapshot.
|
|
86
104
|
- `POST /api/chat/requests` creates a review-required local artifact only.
|
|
87
105
|
|
|
106
|
+
## 12. Browser-first Workspace Slice
|
|
107
|
+
|
|
108
|
+
`/workspace/` centers the first safe user action: Chat-first capture. It shows local Home pressure from `GET /api/home`, posts review-required requests to `POST /api/chat/requests`, and links to `Open Side Review` for inspection. `/chat/` stays available as the compatibility chat surface.
|
|
109
|
+
|
|
88
110
|
## 9. Safety contract
|
|
89
111
|
|
|
90
112
|
- Local-first.
|
|
91
113
|
- Review-required.
|
|
92
114
|
- No hidden execution.
|
|
93
115
|
- No source wiki mutation.
|
|
94
|
-
-
|
|
116
|
+
- Hosted sync is deferred.
|
|
117
|
+
- Production Discord control is deferred.
|
|
118
|
+
- Marketplace, account, payment, publish, push, and package split work are deferred.
|
|
95
119
|
- No auto-promotion, auto-merge, auto-delete, or auto-push.
|
|
96
120
|
- Browser UI uses self-only CSP, local assets, and text-node rendering.
|
|
97
121
|
|
|
@@ -127,3 +151,18 @@ Required dashboard groups:
|
|
|
127
151
|
- `System Health` - explains local load errors or confirms local review data is readable.
|
|
128
152
|
|
|
129
153
|
The Chat home panel may show compact `Next Safe Actions` and `Recent Requests`, but these are navigation/review aids only. They must not become starter chips, execution shortcuts, provider dispatch, or hidden agent controls.
|
|
154
|
+
|
|
155
|
+
## 13. Workspace completion snapshot - 2026-06-01
|
|
156
|
+
|
|
157
|
+
`GET /api/home` now includes an `operating_snapshot` read model for the browser workbench.
|
|
158
|
+
|
|
159
|
+
Stable fields:
|
|
160
|
+
|
|
161
|
+
- `operating_snapshot.version: 2`
|
|
162
|
+
- `operating_snapshot.shell.motif: Local AI Operating Workbench`
|
|
163
|
+
- `operating_snapshot.shell.regions`: `left_app_rail`, `top_workspace_tabs`, `main_work_area`, `right_inspector`, `bottom_status_strip`
|
|
164
|
+
- `operating_snapshot.route_roles`: `/workspace/` as canonical operating workbench, `/chat/` as compatibility inbox, `/side-review/` as review desk
|
|
165
|
+
- `operating_snapshot.artifact_lifecycle`: capture request -> local artifact -> Side Review card -> prepare-only decision artifact -> future explicit approval flow
|
|
166
|
+
- `review_queue.side_review_decision_recent`: latest prepared decision artifacts for the right inspector/Home summary
|
|
167
|
+
|
|
168
|
+
The snapshot is a read model only. It must not become an execution queue, approval queue, hosted sync contract, or source wiki mutation path.
|
|
@@ -6,14 +6,20 @@
|
|
|
6
6
|
|
|
7
7
|
## 1. Simple metaphor
|
|
8
8
|
|
|
9
|
-
Chat is the inbox. Side Review is the review desk.
|
|
9
|
+
EOS Workspace is the browser-first Home. Chat is the inbox. Side Review is the review desk.
|
|
10
10
|
|
|
11
|
-
The user writes a request
|
|
11
|
+
The user writes a request through Chat-first capture on `/workspace/` or the compatibility `/chat/` surface. Eos files it as a local review item, adds a deterministic intent hint, and places it on the `Side Review` desk. Nothing is executed or applied just because the request was captured.
|
|
12
|
+
|
|
13
|
+
The UI motif is a Local AI Operating Workbench:
|
|
14
|
+
|
|
15
|
+
- `/workspace/` is the canonical operating workbench with the left app rail, top workspace tabs, main work area, right inspector, and bottom status strip.
|
|
16
|
+
- `/chat/` is the compatibility inbox for focused capture.
|
|
17
|
+
- `/side-review/` is the review desk for review-required artifacts, evidence, prepared decisions, and completion reports.
|
|
12
18
|
|
|
13
19
|
## 2. Operating loop
|
|
14
20
|
|
|
15
21
|
```text
|
|
16
|
-
User request in
|
|
22
|
+
User request in EOS Workspace Chat-first capture
|
|
17
23
|
-> local capture endpoint
|
|
18
24
|
-> chat request artifact
|
|
19
25
|
-> intent hint metadata
|
|
@@ -51,6 +57,9 @@ POST /api/chat/requests
|
|
|
51
57
|
writes .system/eos/chat/requests/{id}.json
|
|
52
58
|
appends .system/eos/chat/audit.jsonl
|
|
53
59
|
|
|
60
|
+
GET /workspace/
|
|
61
|
+
serves the canonical browser Home with Chat-first capture and Open Side Review navigation
|
|
62
|
+
|
|
54
63
|
GET /api/home
|
|
55
64
|
exposes waiting count, intent counts, and recent request summaries
|
|
56
65
|
|
|
@@ -99,3 +108,21 @@ This slice must not add:
|
|
|
99
108
|
`Home` is the mission-control layer between Chat and Side Review. It should tell the user what is waiting, what was recently captured, and whether any local artifact needs attention before the user opens raw files.
|
|
100
109
|
|
|
101
110
|
`Side Review` adds intent filters so chat-derived requests can be inspected by review hint category: knowledge, search, planning, issue, release log, or unknown. These intent filters are visibility controls, not approval controls. Filtering must not apply, promote, merge, delete, push, publish, or mutate source wiki entries.
|
|
111
|
+
|
|
112
|
+
## 9. Workspace Home coordination
|
|
113
|
+
|
|
114
|
+
`/workspace/` is the canonical browser Home for this slice. It exposes `EOS Workspace`, `Chat-first capture`, `Capture for Review`, `Open Chat`, and `Open Side Review` while keeping every output local-only, review-required, and Not Applied.
|
|
115
|
+
|
|
116
|
+
The review-required artifact lifecycle remains capture request -> local artifact -> Side Review card -> prepare-only decision artifact -> future explicit approval flow. Hosted sync and production Discord control are deferred; marketplace, package split, publish, push, hidden execution, and source wiki mutation stay out of scope.
|
|
117
|
+
|
|
118
|
+
## 10. Completion proof - 2026-06-01
|
|
119
|
+
|
|
120
|
+
The operating loop is now verified as a browser-first product slice:
|
|
121
|
+
|
|
122
|
+
- A request can be captured through the local HTTP/browser flow and surfaced as Home queue pressure.
|
|
123
|
+
- The captured request appears in Side Review with evidence and safe prepare-only actions.
|
|
124
|
+
- Preparing a decision writes `.system/eos/side-review/decisions/{id}.json` and Home shows it as a recent prepared decision.
|
|
125
|
+
- Source markdown checksums remain unchanged; only `.system/eos` review artifacts are written.
|
|
126
|
+
- Chrome desktop/mobile evidence confirms the workspace shell and route-specific surfaces render as nonblank local pages.
|
|
127
|
+
|
|
128
|
+
Completion evidence lives under `.omo/ulw-loop/evidence/eos-workspace-completion-20260601/`.
|
|
@@ -22,6 +22,22 @@ Eos is **not** a cloud assistant surface, marketing landing page, autonomous exe
|
|
|
22
22
|
|
|
23
23
|
The interface should feel like a trustworthy local cockpit where conversation opens separate work panels, not a generic chatbot SaaS clone or button-heavy dashboard.
|
|
24
24
|
|
|
25
|
+
**Local AI Operating Workbench** is the browser-service shell motif for EOS Workspace. It borrows the familiar shape of operating tools without turning EOS into a VS Code extension:
|
|
26
|
+
|
|
27
|
+
- left app rail: persistent navigation for Workspace Home, Chat, Side Review, future evidence views, and status surfaces.
|
|
28
|
+
- top workspace tabs: multiple open work surfaces can be kept visible like browser or IDE tabs.
|
|
29
|
+
- main work area: the active surface owns the center of the screen and carries the primary task.
|
|
30
|
+
- right inspector: optional evidence, artifact metadata, source path, decision detail, and review context.
|
|
31
|
+
- bottom status strip: local-only state, review-required mode, Not Applied state, harness readiness, and route/auth status.
|
|
32
|
+
|
|
33
|
+
Route roles stay explicit:
|
|
34
|
+
|
|
35
|
+
- `/workspace/` is the canonical operating workbench and default browser Home.
|
|
36
|
+
- `/chat/` is the compatibility inbox and focused capture surface.
|
|
37
|
+
- `/side-review/` is the review desk for artifacts, evidence, prepared decisions, and completion reports.
|
|
38
|
+
|
|
39
|
+
The review-required artifact lifecycle is: capture request -> local artifact -> Side Review card -> prepare-only decision artifact -> future explicit approval flow. Hosted sync, production Discord control, marketplace work, package split, publish, push, real execution, and automatic mutation are deferred until separate specs authorize them.
|
|
40
|
+
|
|
25
41
|
Compatibility note: the VS Code extension design gate still tracks the earlier `Dawn Control Room` metaphor and the Korean chat prompt `무엇을 맡길까요?`. Treat both as compatible markers for the same calm, chat-first local command center direction, not as permission to add a dashboard-first or execution-first surface.
|
|
26
42
|
|
|
27
43
|
## Primary Surfaces for This Slice
|
|
@@ -298,3 +314,28 @@ Verification anchors:
|
|
|
298
314
|
- `test/eos/home-data.test.js`
|
|
299
315
|
- `test/eos/side-review-routes.test.js`
|
|
300
316
|
- `test/eos/workspace-boundary-side-review-view-model.test.js`
|
|
317
|
+
|
|
318
|
+
## Implemented EOS Workspace Completion Slice - 2026-06-01
|
|
319
|
+
|
|
320
|
+
The EOS Workspace completion slice turns `/workspace/` into the canonical browser-first operating workbench.
|
|
321
|
+
|
|
322
|
+
What changed:
|
|
323
|
+
|
|
324
|
+
- `GET /api/home` now exposes operating snapshot v2 with the `Local AI Operating Workbench` motif, route roles, artifact lifecycle, recent chat requests, and recent prepared Side Review decisions.
|
|
325
|
+
- `/workspace/` now renders the shell regions directly: left app rail, top workspace tabs, main work area, right inspector, and bottom status strip.
|
|
326
|
+
- Chat capture remains available from `/workspace/` and `/chat/`; both routes create review-required local artifacts only.
|
|
327
|
+
- Prepared Side Review decisions are visible from Home/Workspace as local review-required artifacts.
|
|
328
|
+
- Desktop and mobile Chrome evidence is captured under `.omo/ulw-loop/evidence/eos-workspace-completion-20260601/`.
|
|
329
|
+
|
|
330
|
+
Boundary:
|
|
331
|
+
|
|
332
|
+
- The slice remains web-service first, not a VS Code extension.
|
|
333
|
+
- It does not add hosted sync, production Discord control, marketplace/account/payment features, package splitting, npm publish, auto-apply, auto-promotion, auto-merge/delete, auto-push, hidden execution, or source wiki mutation.
|
|
334
|
+
|
|
335
|
+
Verification anchors:
|
|
336
|
+
|
|
337
|
+
- `test/eos/home-data.test.js`
|
|
338
|
+
- `test/eos/workspace-home-routes.test.js`
|
|
339
|
+
- `test/eos/uiux-product-shape.test.js`
|
|
340
|
+
- `npm run test:eos`
|
|
341
|
+
- `npm run verify:eos-workspace-completion`
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
## 1. Purpose
|
|
8
8
|
|
|
9
|
-
`Side Review` is the companion window for looking at structured material while `Eos Chat` remains the
|
|
9
|
+
`Side Review` is the companion window for looking at structured material while `EOS Workspace` at `/workspace/` remains the browser-first Home and `Eos Chat` remains the compatibility capture surface.
|
|
10
|
+
|
|
11
|
+
In the Local AI Operating Workbench shell, `/side-review/` is the review desk. It can be opened from the left app rail or held as a top workspace tab while the main work area stays on Chat or Home. Its cards may feed the right inspector with artifact paths, evidence, and prepared decision detail, while the bottom status strip continues to show Local Only, Review Required, and Not Applied state.
|
|
10
12
|
|
|
11
13
|
It answers five questions:
|
|
12
14
|
|
|
@@ -291,6 +293,7 @@ Implementation files:
|
|
|
291
293
|
User guidance:
|
|
292
294
|
|
|
293
295
|
1. Open `/side-review/` from the local Eos server.
|
|
296
|
+
1. Use `/workspace/` as the primary Home when returning to Chat-first capture.
|
|
294
297
|
2. Use group tabs to inspect Review Required, Decision Artifacts, Drafts, Completion Reports, Issues, and Harness Status.
|
|
295
298
|
3. Read source, artifact, and evidence paths before taking a decision action.
|
|
296
299
|
4. Use `Prepare Approval Request`, `Prepare Rejection Note`, or `Defer Review` to create a local review artifact only.
|
|
@@ -355,3 +358,21 @@ The UI and API may summarize these proof fields, but must not convert them into
|
|
|
355
358
|
### Future boundary
|
|
356
359
|
|
|
357
360
|
Real approval execution, source wiki mutation, promotion, merge, delete, push, release, npm publish, hosted sync, and Discord runtime control require a separate explicit product spec and are not part of this MVP.
|
|
361
|
+
|
|
362
|
+
## 13. Workspace completion integration - 2026-06-01
|
|
363
|
+
|
|
364
|
+
`Side Review` is now part of the EOS operating workbench loop rather than a standalone browser surface.
|
|
365
|
+
|
|
366
|
+
Integration contract:
|
|
367
|
+
|
|
368
|
+
- `/workspace/` links to Side Review as the review desk in the left app rail and top workspace tabs.
|
|
369
|
+
- Prepared decision artifacts remain in `.system/eos/side-review/decisions/` and are summarized by Home through `review_queue.side_review_decision_recent`.
|
|
370
|
+
- The Workspace right inspector may summarize recent prepared decisions, but it must not expose execution, approval, promotion, merge, delete, push, or source mutation controls.
|
|
371
|
+
- The decision artifact lifecycle remains prepare-only until a future explicit approval flow is specified and separately verified.
|
|
372
|
+
|
|
373
|
+
Verification anchors:
|
|
374
|
+
|
|
375
|
+
- `test/eos/home-data.test.js`
|
|
376
|
+
- `test/eos/side-review-routes.test.js`
|
|
377
|
+
- `test/eos/workspace-home-routes.test.js`
|
|
378
|
+
- `test/eos/uiux-product-shape.test.js`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Eos Visual Screenshot Harness MVP
|
|
2
2
|
|
|
3
3
|
**Status:** MVP harness for local visual evidence
|
|
4
|
-
**Scope:** Eos Chat, Mission Control, and Side Review
|
|
4
|
+
**Scope:** EOS Workspace, Eos Chat, Mission Control, and Side Review
|
|
5
5
|
**Boundary:** local-only screenshot capture; no cloud browser, no hosted sync, no source wiki mutation, no publish, no push
|
|
6
6
|
|
|
7
7
|
## Purpose
|
|
@@ -14,7 +14,7 @@ It is intentionally a release-readiness aid, not a new runtime feature:
|
|
|
14
14
|
- seeds a temporary wiki fixture with safe review-required data,
|
|
15
15
|
- captures PNG screenshots for local surfaces,
|
|
16
16
|
- can capture desktop, tablet, and mobile viewport presets,
|
|
17
|
-
- records a static accessibility snapshot for Eos Chat and Side Review,
|
|
17
|
+
- records a static accessibility snapshot for EOS Workspace, Eos Chat, and Side Review,
|
|
18
18
|
- writes ignored local artifacts under `.system/eos/visual-qa/screenshots/`,
|
|
19
19
|
- shuts down and removes temporary wiki/profile data.
|
|
20
20
|
|
|
@@ -46,6 +46,7 @@ npm run eos:visual-screenshots
|
|
|
46
46
|
|
|
47
47
|
| Target | URL | Desktop output |
|
|
48
48
|
| --- | --- | --- |
|
|
49
|
+
| EOS Workspace Home | `/workspace/` | `eos-workspace-home.png` |
|
|
49
50
|
| Eos Chat + Mission Control | `/chat/` | `eos-chat-command-center.png` |
|
|
50
51
|
| Eos Side Review | `/side-review/` | `eos-side-review.png` |
|
|
51
52
|
|
|
@@ -65,7 +66,7 @@ Viewport-specific files append the viewport id, for example `eos-chat-command-ce
|
|
|
65
66
|
npm run eos:a11y-snapshot
|
|
66
67
|
```
|
|
67
68
|
|
|
68
|
-
The accessibility snapshot is static and local. It checks Eos Chat and Side Review markup/CSS/scripts for baseline review affordances such as language metadata, viewport metadata, skip links, landmarks, ARIA labels, live status regions, focus-visible styling, reduced-motion support, forced-colors support, no broad `transition: all`, no inline event handlers, and no `innerHTML` assignment.
|
|
69
|
+
The accessibility snapshot is static and local. It checks EOS Workspace, Eos Chat, and Side Review markup/CSS/scripts for baseline review affordances such as language metadata, viewport metadata, skip links, landmarks, ARIA labels, live status regions, focus-visible styling, reduced-motion support, forced-colors support, no broad `transition: all`, no inline event handlers, and no `innerHTML` assignment.
|
|
69
70
|
|
|
70
71
|
## Review contract
|
|
71
72
|
|
|
@@ -58,17 +58,48 @@ The following remain intentionally out of scope for this boundary MVP:
|
|
|
58
58
|
|
|
59
59
|
Run from the repo root:
|
|
60
60
|
|
|
61
|
+
```powershell
|
|
62
|
+
npm run verify:eos-workspace-product
|
|
63
|
+
npm run test:eos
|
|
64
|
+
npm run verify:eos-boundary
|
|
65
|
+
npm run verify:eos-boundary-policy
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Expected verifier sentinels include:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
EOS_WORKSPACE_BOUNDARY_VERIFY_OK
|
|
72
|
+
EOS_BOUNDARY_POLICY_VERIFY_OK
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For the historical integrated plugin path, run:
|
|
76
|
+
|
|
61
77
|
```powershell
|
|
62
78
|
npm run verify:eos-boundary --prefix plugins/oh-my-llmwikimode
|
|
63
79
|
node --test plugins/oh-my-llmwikimode/test/eos/workspace-boundary-*.test.js
|
|
64
80
|
```
|
|
65
81
|
|
|
66
|
-
|
|
82
|
+
## Browser Workspace product slice
|
|
83
|
+
|
|
84
|
+
Start the local daemon from the repo root with an explicit local wiki root:
|
|
85
|
+
|
|
86
|
+
```powershell
|
|
87
|
+
$env:EOS_WIKI_ROOT = "$HOME\Documents\llm-wiki"
|
|
88
|
+
node src\daemon.js
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The default daemon address is `http://127.0.0.1:4825`. Open the browser-first Workspace Home at:
|
|
67
92
|
|
|
68
93
|
```text
|
|
69
|
-
|
|
94
|
+
http://127.0.0.1:4825/workspace/
|
|
70
95
|
```
|
|
71
96
|
|
|
97
|
+
The Workspace Home posts chat-first capture requests to `POST /api/chat/requests`. A successful capture writes a local review-required artifact under `.system/eos/chat/requests/` and does not execute, apply, promote, merge, delete, push, or mutate source wiki entries.
|
|
98
|
+
|
|
99
|
+
Inspect the review queue with `GET /api/side-review` or by opening `http://127.0.0.1:4825/side-review/`. Preparing a Side Review decision writes a local artifact under `.system/eos/side-review/decisions/`; prepared decision artifacts are still `review_required` and not applied.
|
|
100
|
+
|
|
101
|
+
Product-slice evidence for this implementation is captured under `.omo/ulw-loop/evidence/eos-workspace-product-20260601`. The evidence root is local review material only and is safe to clean after review if no longer needed.
|
|
102
|
+
|
|
72
103
|
## Static boundary policy
|
|
73
104
|
|
|
74
105
|
Run `npm run verify:eos-boundary-policy` to prevent new unregistered private imports across the Eos/LLM Wiki boundary. The verifier is local-only and does not publish, push, sync, or call external services.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-llmwikimode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "LLM Wiki plugin for OpenCode ??auto-memory, retrieval, and knowledge promotion",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/server.js",
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
"test:storage": "node --test test/storage.test.js",
|
|
29
29
|
"test:security": "node --test test/security.test.js",
|
|
30
30
|
"test:eos": "node --test test/eos/**/*.test.js",
|
|
31
|
+
"verify:eos-workspace-product": "npm run test:eos && npm run verify:eos-boundary-policy && npm run verify:eos-boundary && npm run eos:a11y-snapshot -- --json",
|
|
32
|
+
"verify:eos-workspace-completion": "node scripts/verify-eos-workspace-completion.js",
|
|
31
33
|
"verify:package": "node test/verify-package.test.js",
|
|
32
34
|
"verify:docs": "node test/verify-docs.test.js",
|
|
33
35
|
"verify:side-effects": "node scripts/side-effect-audit.js",
|
package/src/eos/home.js
CHANGED
|
@@ -11,6 +11,27 @@ import {
|
|
|
11
11
|
} from "../llmwiki/eos-adapter.js";
|
|
12
12
|
import { classifyChatIntent } from "./chat/intent-hints.js";
|
|
13
13
|
|
|
14
|
+
const OPERATING_SNAPSHOT_VERSION = 2;
|
|
15
|
+
const WORKSPACE_SHELL_REGIONS = Object.freeze([
|
|
16
|
+
Object.freeze({ id: "left_app_rail", label: "Left app rail", role: "persistent_navigation" }),
|
|
17
|
+
Object.freeze({ id: "top_workspace_tabs", label: "Top workspace tabs", role: "workspace_context" }),
|
|
18
|
+
Object.freeze({ id: "main_work_area", label: "Main work area", role: "primary_task_surface" }),
|
|
19
|
+
Object.freeze({ id: "right_inspector", label: "Right inspector", role: "evidence_and_detail" }),
|
|
20
|
+
Object.freeze({ id: "bottom_status_strip", label: "Bottom status strip", role: "local_safety_status" }),
|
|
21
|
+
]);
|
|
22
|
+
const ROUTE_ROLES = Object.freeze({
|
|
23
|
+
workspace: "canonical_operating_workbench",
|
|
24
|
+
chat: "compatibility_inbox",
|
|
25
|
+
side_review: "review_desk",
|
|
26
|
+
});
|
|
27
|
+
const ARTIFACT_LIFECYCLE = Object.freeze([
|
|
28
|
+
"capture_request",
|
|
29
|
+
"local_artifact",
|
|
30
|
+
"side_review_card",
|
|
31
|
+
"prepare_only_decision_artifact",
|
|
32
|
+
"future_explicit_approval_flow",
|
|
33
|
+
]);
|
|
34
|
+
|
|
14
35
|
function readJson(filePath, fallback = {}) {
|
|
15
36
|
try {
|
|
16
37
|
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
@@ -44,6 +65,8 @@ function buildReviewQueue(wikiRoot) {
|
|
|
44
65
|
const graphInsightCount = safeCount(graphReasoning.summary?.insight_count ?? graphReasoning.insights?.length);
|
|
45
66
|
const chatRequests = summarizeChatRequestsForHome(wikiRoot);
|
|
46
67
|
const chatRequestCount = chatRequests.total;
|
|
68
|
+
const sideReviewDecisions = summarizeSideReviewDecisionsForHome(wikiRoot);
|
|
69
|
+
const sideReviewDecisionCount = sideReviewDecisions.total;
|
|
47
70
|
|
|
48
71
|
return {
|
|
49
72
|
curator_queue_count: curatorQueueCount,
|
|
@@ -54,7 +77,15 @@ function buildReviewQueue(wikiRoot) {
|
|
|
54
77
|
chat_request_intent_counts: chatRequests.intent_counts,
|
|
55
78
|
chat_request_recent: chatRequests.recent,
|
|
56
79
|
chat_request_load_errors: chatRequests.load_errors,
|
|
57
|
-
|
|
80
|
+
side_review_decision_count: sideReviewDecisionCount,
|
|
81
|
+
side_review_decision_recent: sideReviewDecisions.recent,
|
|
82
|
+
side_review_decision_load_errors: sideReviewDecisions.load_errors,
|
|
83
|
+
total_review_required: curatorQueueCount
|
|
84
|
+
+ curatorProposalCount
|
|
85
|
+
+ graphQueueCardCount
|
|
86
|
+
+ graphInsightCount
|
|
87
|
+
+ chatRequestCount
|
|
88
|
+
+ sideReviewDecisionCount,
|
|
58
89
|
};
|
|
59
90
|
}
|
|
60
91
|
|
|
@@ -144,6 +175,95 @@ function buildCommandCenter(reviewQueue, librarian, actions) {
|
|
|
144
175
|
};
|
|
145
176
|
}
|
|
146
177
|
|
|
178
|
+
function buildWorkspaceHome() {
|
|
179
|
+
return {
|
|
180
|
+
title: "EOS Workspace",
|
|
181
|
+
route: "/workspace/",
|
|
182
|
+
operating_snapshot_version: OPERATING_SNAPSHOT_VERSION,
|
|
183
|
+
shell: {
|
|
184
|
+
motif: "Local AI Operating Workbench",
|
|
185
|
+
regions: WORKSPACE_SHELL_REGIONS,
|
|
186
|
+
},
|
|
187
|
+
route_roles: ROUTE_ROLES,
|
|
188
|
+
artifact_lifecycle: ARTIFACT_LIFECYCLE,
|
|
189
|
+
primary_surface: "chat_capture",
|
|
190
|
+
companion_surface: "side_review",
|
|
191
|
+
capture_endpoint: "/api/chat/requests",
|
|
192
|
+
home_endpoint: "/api/home",
|
|
193
|
+
side_review_endpoint: "/api/side-review",
|
|
194
|
+
safe_copy: {
|
|
195
|
+
primary: "Chat-first capture",
|
|
196
|
+
safety: "Local Only / Review Required / Not Applied",
|
|
197
|
+
capture: "Capture for Review",
|
|
198
|
+
review: "Open Side Review",
|
|
199
|
+
},
|
|
200
|
+
nav: [
|
|
201
|
+
{
|
|
202
|
+
id: "chat",
|
|
203
|
+
label: "Open Chat",
|
|
204
|
+
href: "/chat/",
|
|
205
|
+
target: "compatibility_chat",
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: "side-review",
|
|
209
|
+
label: "Open Side Review",
|
|
210
|
+
href: "/side-review/",
|
|
211
|
+
target: "side_review",
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
forbidden_capabilities: [
|
|
215
|
+
"hidden_execution",
|
|
216
|
+
"hosted_sync",
|
|
217
|
+
"auto_promote",
|
|
218
|
+
"auto_merge",
|
|
219
|
+
"auto_delete",
|
|
220
|
+
"auto_push",
|
|
221
|
+
],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function summarizeSideReviewDecisionsForHome(wikiRoot) {
|
|
226
|
+
const recent = [];
|
|
227
|
+
const loadErrors = [];
|
|
228
|
+
let entries;
|
|
229
|
+
const decisionsDir = path.join(wikiRoot, ".system", "eos", "side-review", "decisions");
|
|
230
|
+
try {
|
|
231
|
+
entries = fs.readdirSync(decisionsDir, { withFileTypes: true });
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (error?.code === "ENOENT") {
|
|
234
|
+
return { total: 0, recent: [], load_errors: [] };
|
|
235
|
+
}
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for (const entry of entries) {
|
|
240
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
241
|
+
try {
|
|
242
|
+
const filePath = path.join(decisionsDir, entry.name);
|
|
243
|
+
const artifact = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
244
|
+
if (artifact?.type !== "eos_side_review_decision" || artifact?.status !== "review_required") continue;
|
|
245
|
+
recent.push({
|
|
246
|
+
id: artifact.id,
|
|
247
|
+
card_id: artifact.card_id,
|
|
248
|
+
decision: artifact.decision,
|
|
249
|
+
decision_label: artifact.decision_label || artifact.decision || "review decision",
|
|
250
|
+
status: artifact.status,
|
|
251
|
+
artifact_path: artifact.artifact_path,
|
|
252
|
+
source_artifact_path: artifact.source_artifact_path || null,
|
|
253
|
+
source_path: artifact.source_path || null,
|
|
254
|
+
created_at: artifact.created_at || null,
|
|
255
|
+
});
|
|
256
|
+
} catch (error) {
|
|
257
|
+
loadErrors.push({
|
|
258
|
+
path: path.posix.join(".system", "eos", "side-review", "decisions", entry.name),
|
|
259
|
+
error: error instanceof Error ? error.message : String(error),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
recent.sort((left, right) => String(right.created_at || "").localeCompare(String(left.created_at || "")) || String(left.id).localeCompare(String(right.id)));
|
|
264
|
+
return { total: recent.length, recent: recent.slice(0, 5), load_errors: loadErrors };
|
|
265
|
+
}
|
|
266
|
+
|
|
147
267
|
function summarizeChatRequestsForHome(wikiRoot) {
|
|
148
268
|
const counts = {
|
|
149
269
|
knowledge_request: 0,
|
|
@@ -283,6 +403,7 @@ export function buildEosHomeData(wikiRoot, config = {}, options = {}) {
|
|
|
283
403
|
const reviewQueue = buildReviewQueue(wikiRoot);
|
|
284
404
|
const librarian = buildLibrarianState(wikiRoot, config, options);
|
|
285
405
|
const nextSafeActions = buildNextSafeActions(reviewQueue, librarian);
|
|
406
|
+
const workspaceHome = buildWorkspaceHome();
|
|
286
407
|
|
|
287
408
|
return {
|
|
288
409
|
version: 1,
|
|
@@ -296,6 +417,20 @@ export function buildEosHomeData(wikiRoot, config = {}, options = {}) {
|
|
|
296
417
|
recent_entries: Array.isArray(stats.recent_entries) ? stats.recent_entries.slice(0, 8) : [],
|
|
297
418
|
},
|
|
298
419
|
review_queue: reviewQueue,
|
|
420
|
+
workspace_home: workspaceHome,
|
|
421
|
+
operating_snapshot: {
|
|
422
|
+
version: OPERATING_SNAPSHOT_VERSION,
|
|
423
|
+
shell: workspaceHome.shell,
|
|
424
|
+
route_roles: workspaceHome.route_roles,
|
|
425
|
+
artifact_lifecycle: workspaceHome.artifact_lifecycle,
|
|
426
|
+
recent_requests: reviewQueue.chat_request_recent,
|
|
427
|
+
recent_decisions: reviewQueue.side_review_decision_recent,
|
|
428
|
+
safety: {
|
|
429
|
+
local_only: true,
|
|
430
|
+
review_required: true,
|
|
431
|
+
not_applied: true,
|
|
432
|
+
},
|
|
433
|
+
},
|
|
299
434
|
librarian,
|
|
300
435
|
command_center: buildCommandCenter(reviewQueue, librarian, nextSafeActions),
|
|
301
436
|
search: {
|
package/src/eos/http/app.js
CHANGED
|
@@ -74,7 +74,8 @@ function getPathname(url) {
|
|
|
74
74
|
function isLocalStaticUiRoute(url) {
|
|
75
75
|
const pathname = getPathname(url);
|
|
76
76
|
return pathname === "/side-review" || pathname.startsWith("/side-review/") ||
|
|
77
|
-
pathname === "/chat" || pathname.startsWith("/chat/")
|
|
77
|
+
pathname === "/chat" || pathname.startsWith("/chat/") ||
|
|
78
|
+
pathname === "/workspace" || pathname.startsWith("/workspace/");
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
function isGuardedLocalMutationRoute(url) {
|
|
@@ -246,6 +247,15 @@ export async function buildApp(config, logger) {
|
|
|
246
247
|
});
|
|
247
248
|
}
|
|
248
249
|
|
|
250
|
+
const workspaceStaticDir = path.resolve(__dirname, "../workspace/static");
|
|
251
|
+
if (fs.existsSync(workspaceStaticDir)) {
|
|
252
|
+
await app.register(fastifyStatic, {
|
|
253
|
+
root: workspaceStaticDir,
|
|
254
|
+
prefix: "/workspace/",
|
|
255
|
+
decorateReply: false,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
249
259
|
// -----------------------------------------------------------------------
|
|
250
260
|
// Body parser
|
|
251
261
|
// -----------------------------------------------------------------------
|
|
@@ -257,8 +267,9 @@ export async function buildApp(config, logger) {
|
|
|
257
267
|
// -----------------------------------------------------------------------
|
|
258
268
|
|
|
259
269
|
app.setErrorHandler((error, _request, reply) => {
|
|
260
|
-
const
|
|
261
|
-
const
|
|
270
|
+
const invalidJson = error instanceof SyntaxError && /^Invalid JSON:/.test(error.message || "");
|
|
271
|
+
const statusCode = invalidJson ? 400 : error.statusCode || 500;
|
|
272
|
+
const code = invalidJson ? "BAD_REQUEST" : error.code || "INTERNAL_ERROR";
|
|
262
273
|
const message =
|
|
263
274
|
statusCode >= 500 ? "Internal server error" : error.message;
|
|
264
275
|
|
|
@@ -300,6 +311,8 @@ export async function buildApp(config, logger) {
|
|
|
300
311
|
await registerAgentRoutes(app, config);
|
|
301
312
|
await registerSideReviewRoutes(app, config);
|
|
302
313
|
|
|
314
|
+
app.get("/workspace", async (_request, reply) => reply.redirect("/workspace/"));
|
|
315
|
+
|
|
303
316
|
// -----------------------------------------------------------------------
|
|
304
317
|
// 404 handler (must be registered after all routes)
|
|
305
318
|
// -----------------------------------------------------------------------
|
|
@@ -93,7 +93,7 @@ textarea {
|
|
|
93
93
|
color: var(--eos-safe);
|
|
94
94
|
font-size: 0.78rem;
|
|
95
95
|
font-weight: 800;
|
|
96
|
-
letter-spacing: 0
|
|
96
|
+
letter-spacing: 0;
|
|
97
97
|
text-transform: uppercase;
|
|
98
98
|
}
|
|
99
99
|
|
|
@@ -106,7 +106,7 @@ h1 {
|
|
|
106
106
|
margin-bottom: 16px;
|
|
107
107
|
font-size: clamp(2.4rem, 7vw, 5.4rem);
|
|
108
108
|
line-height: 0.92;
|
|
109
|
-
letter-spacing:
|
|
109
|
+
letter-spacing: 0;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
.lede {
|
|
@@ -356,7 +356,7 @@ code {
|
|
|
356
356
|
color: var(--eos-muted);
|
|
357
357
|
font-size: 0.78rem;
|
|
358
358
|
font-weight: 800;
|
|
359
|
-
letter-spacing: 0
|
|
359
|
+
letter-spacing: 0;
|
|
360
360
|
text-transform: uppercase;
|
|
361
361
|
}
|
|
362
362
|
|
|
@@ -462,7 +462,7 @@ textarea {
|
|
|
462
462
|
h1 {
|
|
463
463
|
font-size: clamp(2rem, 11vw, 2.8rem);
|
|
464
464
|
line-height: 1.02;
|
|
465
|
-
letter-spacing:
|
|
465
|
+
letter-spacing: 0;
|
|
466
466
|
}
|
|
467
467
|
|
|
468
468
|
.lede {
|
|
@@ -2,6 +2,13 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
const SURFACES = Object.freeze([
|
|
5
|
+
Object.freeze({
|
|
6
|
+
id: "workspace-home",
|
|
7
|
+
label: "EOS Workspace Home",
|
|
8
|
+
html: "src/eos/workspace/static/index.html",
|
|
9
|
+
css: "src/eos/workspace/static/styles.css",
|
|
10
|
+
script: "src/eos/workspace/static/app.js",
|
|
11
|
+
}),
|
|
5
12
|
Object.freeze({
|
|
6
13
|
id: "chat-command-center",
|
|
7
14
|
label: "Eos Chat + Mission Control",
|
|
@@ -24,6 +24,13 @@ const VIEWPORTS = Object.freeze([
|
|
|
24
24
|
]);
|
|
25
25
|
|
|
26
26
|
const DEFAULT_TARGETS = [
|
|
27
|
+
{
|
|
28
|
+
id: "workspace-home",
|
|
29
|
+
label: "EOS Workspace Home",
|
|
30
|
+
path: "/workspace/",
|
|
31
|
+
fileName: "eos-workspace-home.png",
|
|
32
|
+
captures: ["workspace_home", "chat_first_capture"],
|
|
33
|
+
},
|
|
27
34
|
{
|
|
28
35
|
id: "chat-command-center",
|
|
29
36
|
label: "Eos Chat + Mission Control",
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const state = {
|
|
2
|
+
loadingHome: false,
|
|
3
|
+
capturing: false,
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
const elements = {
|
|
7
|
+
form: document.querySelector("#capture-form"),
|
|
8
|
+
input: document.querySelector("#request-input"),
|
|
9
|
+
resultEmpty: document.querySelector("#result-empty"),
|
|
10
|
+
resultList: document.querySelector("#result-list"),
|
|
11
|
+
statusGrid: document.querySelector("#status-grid"),
|
|
12
|
+
wikiCount: document.querySelector("#wiki-count"),
|
|
13
|
+
reviewCount: document.querySelector("#review-count"),
|
|
14
|
+
intentSummary: document.querySelector("#intent-summary"),
|
|
15
|
+
healthSummary: document.querySelector("#health-summary"),
|
|
16
|
+
decisionSummary: document.querySelector("#decision-summary"),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function setText(node, value) {
|
|
20
|
+
if (node) node.textContent = value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function syncBusyState() {
|
|
24
|
+
const button = elements.form?.querySelector("button");
|
|
25
|
+
if (button) button.disabled = state.capturing;
|
|
26
|
+
if (elements.statusGrid) {
|
|
27
|
+
const busy = state.loadingHome || state.capturing;
|
|
28
|
+
elements.statusGrid.setAttribute("aria-busy", busy ? "true" : "false");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function setLoadingHome(value) {
|
|
33
|
+
state.loadingHome = value;
|
|
34
|
+
syncBusyState();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function setCapturing(value) {
|
|
38
|
+
state.capturing = value;
|
|
39
|
+
syncBusyState();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function formatIntentSummary(counts = {}) {
|
|
43
|
+
const labels = [
|
|
44
|
+
["issue_report", "Issues"],
|
|
45
|
+
["planning_request", "Plans"],
|
|
46
|
+
["knowledge_request", "Knowledge"],
|
|
47
|
+
["search_request", "Search"],
|
|
48
|
+
["release_log", "Release"],
|
|
49
|
+
];
|
|
50
|
+
const visible = labels
|
|
51
|
+
.map(([key, label]) => [label, Number(counts[key] || 0)])
|
|
52
|
+
.filter(([, count]) => count > 0)
|
|
53
|
+
.slice(0, 3);
|
|
54
|
+
if (visible.length === 0) return "No captured requests";
|
|
55
|
+
return visible.map(([label, count]) => `${label} ${count}`).join(" / ");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function formatDecisionSummary(decisions = []) {
|
|
59
|
+
if (!Array.isArray(decisions) || decisions.length === 0) return "No prepared decisions yet";
|
|
60
|
+
const first = decisions[0];
|
|
61
|
+
const decision = first.decision_label || first.decision || "review decision";
|
|
62
|
+
const suffix = decisions.length === 1 ? "decision" : "decisions";
|
|
63
|
+
return `${decisions.length} prepared ${suffix}; latest is ${decision}.`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function appendCaptureResult(result) {
|
|
67
|
+
if (!elements.resultList) return;
|
|
68
|
+
if (elements.resultEmpty) elements.resultEmpty.hidden = true;
|
|
69
|
+
|
|
70
|
+
const item = document.createElement("li");
|
|
71
|
+
item.className = "result-item";
|
|
72
|
+
|
|
73
|
+
const title = document.createElement("strong");
|
|
74
|
+
title.textContent = "Captured for review.";
|
|
75
|
+
item.append(title);
|
|
76
|
+
|
|
77
|
+
const intent = document.createElement("p");
|
|
78
|
+
intent.textContent = `Intent hint: ${result.intent_hint?.label || "Unknown Intent"}. Nothing was applied.`;
|
|
79
|
+
item.append(intent);
|
|
80
|
+
|
|
81
|
+
const artifactPath = result.artifactPath || result.request?.artifact_path || "artifact path unavailable";
|
|
82
|
+
const pathLine = document.createElement("p");
|
|
83
|
+
pathLine.className = "artifact-path";
|
|
84
|
+
pathLine.textContent = `Artifact path: ${artifactPath}`;
|
|
85
|
+
item.append(pathLine);
|
|
86
|
+
|
|
87
|
+
const link = document.createElement("a");
|
|
88
|
+
link.href = "/side-review/";
|
|
89
|
+
link.textContent = "Open Side Review";
|
|
90
|
+
item.append(link);
|
|
91
|
+
|
|
92
|
+
elements.resultList.prepend(item);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function loadHome() {
|
|
96
|
+
setLoadingHome(true);
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetch("/api/home", { headers: { accept: "application/json" } });
|
|
99
|
+
if (!response.ok) throw new Error(`Home API failed: ${response.status}`);
|
|
100
|
+
const payload = await response.json();
|
|
101
|
+
const home = payload.eos_home || {};
|
|
102
|
+
const queue = home.review_queue || {};
|
|
103
|
+
const health = home.command_center?.health?.description || "Local status available.";
|
|
104
|
+
setText(elements.wikiCount, `${home.wiki?.total_entries ?? 0} entries`);
|
|
105
|
+
setText(elements.reviewCount, `${queue.total_review_required ?? 0} waiting`);
|
|
106
|
+
setText(elements.intentSummary, formatIntentSummary(queue.chat_request_intent_counts));
|
|
107
|
+
setText(elements.healthSummary, health);
|
|
108
|
+
setText(elements.decisionSummary, formatDecisionSummary(queue.side_review_decision_recent));
|
|
109
|
+
} catch {
|
|
110
|
+
setText(elements.wikiCount, "Unavailable");
|
|
111
|
+
setText(elements.reviewCount, "Check local server");
|
|
112
|
+
setText(elements.intentSummary, "Unavailable");
|
|
113
|
+
setText(elements.healthSummary, "Home data is unavailable. Nothing was applied.");
|
|
114
|
+
setText(elements.decisionSummary, "Prepared decisions are unavailable.");
|
|
115
|
+
} finally {
|
|
116
|
+
setLoadingHome(false);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function capture(message) {
|
|
121
|
+
const response = await fetch("/api/chat/requests", {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
124
|
+
body: JSON.stringify({ message, source: "eos_chat" }),
|
|
125
|
+
});
|
|
126
|
+
const payload = await response.json();
|
|
127
|
+
if (!response.ok || payload.success === false) {
|
|
128
|
+
throw new Error(payload.error?.message || payload.error || "Capture request failed.");
|
|
129
|
+
}
|
|
130
|
+
return payload;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function onSubmit(event) {
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
if (state.capturing) return;
|
|
136
|
+
const message = elements.input?.value.trim() || "";
|
|
137
|
+
if (!message) return;
|
|
138
|
+
|
|
139
|
+
setCapturing(true);
|
|
140
|
+
try {
|
|
141
|
+
const result = await capture(message);
|
|
142
|
+
appendCaptureResult(result);
|
|
143
|
+
elements.input.value = "";
|
|
144
|
+
await loadHome();
|
|
145
|
+
} catch (error) {
|
|
146
|
+
appendCaptureResult({
|
|
147
|
+
intent_hint: { label: "Capture needs attention" },
|
|
148
|
+
artifactPath: error instanceof Error ? error.message : String(error),
|
|
149
|
+
});
|
|
150
|
+
} finally {
|
|
151
|
+
setCapturing(false);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (elements.form) {
|
|
156
|
+
elements.form.addEventListener("submit", onSubmit);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
loadHome();
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>EOS Workspace</title>
|
|
7
|
+
<link rel="stylesheet" href="/workspace/styles.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<a class="skip-link" href="#workspace-content">Skip to EOS Workspace content</a>
|
|
11
|
+
<div class="operating-workbench">
|
|
12
|
+
<aside class="app-rail" aria-label="Left app rail">
|
|
13
|
+
<p class="rail-title">Left app rail</p>
|
|
14
|
+
<a class="rail-link is-active" href="/workspace/" aria-current="page">Workspace</a>
|
|
15
|
+
<a class="rail-link" href="/chat/">Chat</a>
|
|
16
|
+
<a class="rail-link" href="/side-review/">Side Review</a>
|
|
17
|
+
</aside>
|
|
18
|
+
|
|
19
|
+
<div class="workspace-frame">
|
|
20
|
+
<nav class="workspace-tabs" aria-label="Top workspace tabs">
|
|
21
|
+
<span class="tab is-active">Top workspace tabs</span>
|
|
22
|
+
<a class="tab" href="/chat/">Chat Capture</a>
|
|
23
|
+
<a class="tab" href="/side-review/">Side Review</a>
|
|
24
|
+
</nav>
|
|
25
|
+
|
|
26
|
+
<main id="workspace-content" class="workspace-shell main-work-area" aria-labelledby="workspace-title">
|
|
27
|
+
<header class="top-row">
|
|
28
|
+
<div>
|
|
29
|
+
<p class="eyebrow">Chat-first capture / Main work area</p>
|
|
30
|
+
<h1 id="workspace-title">EOS Workspace</h1>
|
|
31
|
+
<p class="subtitle">Capture requests to local review artifacts before any source updates.</p>
|
|
32
|
+
</div>
|
|
33
|
+
<nav aria-label="Workspace navigation">
|
|
34
|
+
<a class="nav-link" href="/chat/">Open Chat</a>
|
|
35
|
+
<a class="nav-link" href="/side-review/">Open Side Review</a>
|
|
36
|
+
</nav>
|
|
37
|
+
</header>
|
|
38
|
+
|
|
39
|
+
<section class="safety-strip" aria-label="Safety contract">
|
|
40
|
+
<span>Local Only</span>
|
|
41
|
+
<span>Review Required</span>
|
|
42
|
+
<span>Not Applied</span>
|
|
43
|
+
<strong>Local artifact capture only. Nothing was applied.</strong>
|
|
44
|
+
</section>
|
|
45
|
+
|
|
46
|
+
<section class="status-grid" id="status-grid" aria-busy="true" aria-label="Workspace status">
|
|
47
|
+
<article>
|
|
48
|
+
<p class="label">Wiki Entries</p>
|
|
49
|
+
<strong id="wiki-count">Loading...</strong>
|
|
50
|
+
</article>
|
|
51
|
+
<article>
|
|
52
|
+
<p class="label">Review Queue</p>
|
|
53
|
+
<strong id="review-count">Loading...</strong>
|
|
54
|
+
</article>
|
|
55
|
+
<article>
|
|
56
|
+
<p class="label">Intent Summary</p>
|
|
57
|
+
<strong id="intent-summary">Loading...</strong>
|
|
58
|
+
</article>
|
|
59
|
+
<article>
|
|
60
|
+
<p class="label">Local Health</p>
|
|
61
|
+
<strong id="health-summary">Loading...</strong>
|
|
62
|
+
</article>
|
|
63
|
+
</section>
|
|
64
|
+
|
|
65
|
+
<form id="capture-form" class="capture-form" aria-label="Capture request for local review">
|
|
66
|
+
<label for="request-input">Request</label>
|
|
67
|
+
<textarea id="request-input" name="message" rows="4" maxlength="8000" required placeholder="Tell EOS what to prepare for review."></textarea>
|
|
68
|
+
<button type="submit">Capture for Review</button>
|
|
69
|
+
<p class="help-text">Captured requests remain review-required until you inspect them in Side Review.</p>
|
|
70
|
+
</form>
|
|
71
|
+
|
|
72
|
+
<section class="result-panel" aria-live="polite" aria-label="Capture results">
|
|
73
|
+
<p id="result-empty" class="muted">No captured requests in this browser session.</p>
|
|
74
|
+
<ol id="result-list" class="result-list"></ol>
|
|
75
|
+
</section>
|
|
76
|
+
</main>
|
|
77
|
+
|
|
78
|
+
<aside class="right-inspector" aria-label="Right inspector">
|
|
79
|
+
<p class="eyebrow">Right inspector</p>
|
|
80
|
+
<h2>Prepared Decisions</h2>
|
|
81
|
+
<p id="decision-summary">No prepared decisions yet.</p>
|
|
82
|
+
<p class="muted">Decision artifacts stay local and review-required until an explicit approval flow exists.</p>
|
|
83
|
+
</aside>
|
|
84
|
+
|
|
85
|
+
<footer class="bottom-status" aria-label="Bottom status strip">
|
|
86
|
+
<span>Bottom status strip</span>
|
|
87
|
+
<span>Local Only</span>
|
|
88
|
+
<span>Review Required</span>
|
|
89
|
+
<span>Not Applied</span>
|
|
90
|
+
</footer>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
<script src="/workspace/app.js" defer></script>
|
|
94
|
+
</body>
|
|
95
|
+
</html>
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: dark light;
|
|
3
|
+
--bg: var(--vscode-editor-background, #0f172a);
|
|
4
|
+
--surface: var(--vscode-editorWidget-background, #15243a);
|
|
5
|
+
--surface-soft: var(--vscode-sideBar-background, #112033);
|
|
6
|
+
--text: var(--vscode-foreground, #e2e8f0);
|
|
7
|
+
--muted: var(--vscode-descriptionForeground, #9fb0c3);
|
|
8
|
+
--border: var(--vscode-panel-border, rgba(148, 163, 184, 0.28));
|
|
9
|
+
--accent: var(--vscode-textLink-foreground, #7dd3fc);
|
|
10
|
+
--focus: var(--vscode-focusBorder, #38bdf8);
|
|
11
|
+
--warning: var(--vscode-editorWarning-foreground, #f59e0b);
|
|
12
|
+
font-family: var(--vscode-font-family, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
* {
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
body {
|
|
20
|
+
margin: 0;
|
|
21
|
+
min-height: 100vh;
|
|
22
|
+
color: var(--text);
|
|
23
|
+
background: linear-gradient(180deg, color-mix(in srgb, var(--surface-soft) 90%, transparent), var(--bg));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.operating-workbench {
|
|
27
|
+
min-height: 100vh;
|
|
28
|
+
display: grid;
|
|
29
|
+
grid-template-columns: 184px minmax(0, 1fr);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.app-rail {
|
|
33
|
+
min-width: 0;
|
|
34
|
+
padding: 18px 12px;
|
|
35
|
+
border-right: 1px solid var(--border);
|
|
36
|
+
background: color-mix(in srgb, var(--bg) 82%, black);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.rail-title {
|
|
40
|
+
margin: 0 0 14px;
|
|
41
|
+
color: var(--muted);
|
|
42
|
+
font-size: 0.78rem;
|
|
43
|
+
font-weight: 800;
|
|
44
|
+
text-transform: uppercase;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.rail-link {
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
min-height: 36px;
|
|
51
|
+
margin-bottom: 6px;
|
|
52
|
+
padding: 8px 10px;
|
|
53
|
+
border: 1px solid transparent;
|
|
54
|
+
border-radius: 8px;
|
|
55
|
+
color: var(--text);
|
|
56
|
+
font-weight: 700;
|
|
57
|
+
text-decoration: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.rail-link.is-active,
|
|
61
|
+
.rail-link:focus-visible,
|
|
62
|
+
.rail-link:hover {
|
|
63
|
+
border-color: color-mix(in srgb, var(--accent) 48%, var(--border));
|
|
64
|
+
background: color-mix(in srgb, var(--accent) 14%, var(--surface-soft));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.workspace-frame {
|
|
68
|
+
min-width: 0;
|
|
69
|
+
min-height: 100vh;
|
|
70
|
+
display: grid;
|
|
71
|
+
grid-template-columns: minmax(0, 1fr) minmax(240px, 288px);
|
|
72
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.workspace-tabs {
|
|
76
|
+
grid-column: 1 / -1;
|
|
77
|
+
min-width: 0;
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: end;
|
|
80
|
+
gap: 6px;
|
|
81
|
+
padding: 10px 14px 0;
|
|
82
|
+
border-bottom: 1px solid var(--border);
|
|
83
|
+
background: color-mix(in srgb, var(--surface-soft) 88%, transparent);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.tab {
|
|
87
|
+
min-height: 36px;
|
|
88
|
+
padding: 9px 12px;
|
|
89
|
+
border: 1px solid transparent;
|
|
90
|
+
border-bottom: 0;
|
|
91
|
+
border-radius: 8px 8px 0 0;
|
|
92
|
+
color: var(--muted);
|
|
93
|
+
font-weight: 800;
|
|
94
|
+
text-decoration: none;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.tab.is-active {
|
|
98
|
+
color: var(--text);
|
|
99
|
+
border-color: var(--border);
|
|
100
|
+
background: var(--surface);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.skip-link {
|
|
104
|
+
position: absolute;
|
|
105
|
+
top: 12px;
|
|
106
|
+
left: 12px;
|
|
107
|
+
z-index: 100;
|
|
108
|
+
transform: translateY(-180%);
|
|
109
|
+
padding: 8px 12px;
|
|
110
|
+
border-radius: 8px;
|
|
111
|
+
color: var(--bg);
|
|
112
|
+
background: var(--accent);
|
|
113
|
+
font-weight: 700;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.skip-link:focus-visible {
|
|
117
|
+
transform: translateY(0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.workspace-shell {
|
|
121
|
+
width: 100%;
|
|
122
|
+
min-width: 0;
|
|
123
|
+
min-height: 0;
|
|
124
|
+
padding: 18px;
|
|
125
|
+
display: grid;
|
|
126
|
+
gap: 14px;
|
|
127
|
+
align-content: start;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.workspace-shell > * {
|
|
131
|
+
min-width: 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.main-work-area {
|
|
135
|
+
background: color-mix(in srgb, var(--bg) 54%, transparent);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.right-inspector {
|
|
139
|
+
min-width: 0;
|
|
140
|
+
padding: 18px 14px;
|
|
141
|
+
border-left: 1px solid var(--border);
|
|
142
|
+
background: color-mix(in srgb, var(--surface-soft) 76%, transparent);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.right-inspector h2 {
|
|
146
|
+
margin: 0 0 10px;
|
|
147
|
+
font-size: 1.05rem;
|
|
148
|
+
line-height: 1.2;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.bottom-status {
|
|
152
|
+
grid-column: 1 / -1;
|
|
153
|
+
min-width: 0;
|
|
154
|
+
display: flex;
|
|
155
|
+
flex-wrap: wrap;
|
|
156
|
+
gap: 8px;
|
|
157
|
+
align-items: center;
|
|
158
|
+
padding: 8px 14px;
|
|
159
|
+
border-top: 1px solid var(--border);
|
|
160
|
+
color: var(--muted);
|
|
161
|
+
background: color-mix(in srgb, var(--bg) 82%, black);
|
|
162
|
+
font-size: 0.82rem;
|
|
163
|
+
font-weight: 700;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.bottom-status span:not(:first-child) {
|
|
167
|
+
border: 1px solid var(--border);
|
|
168
|
+
border-radius: 999px;
|
|
169
|
+
padding: 3px 8px;
|
|
170
|
+
color: var(--warning);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.top-row,
|
|
174
|
+
.status-grid article,
|
|
175
|
+
.capture-form,
|
|
176
|
+
.result-panel {
|
|
177
|
+
border: 1px solid var(--border);
|
|
178
|
+
border-radius: 8px;
|
|
179
|
+
background: color-mix(in srgb, var(--surface) 92%, transparent);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.top-row {
|
|
183
|
+
display: flex;
|
|
184
|
+
justify-content: space-between;
|
|
185
|
+
gap: 16px;
|
|
186
|
+
align-items: flex-start;
|
|
187
|
+
padding: 16px;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.eyebrow,
|
|
191
|
+
.label {
|
|
192
|
+
margin: 0 0 6px;
|
|
193
|
+
color: var(--accent);
|
|
194
|
+
text-transform: uppercase;
|
|
195
|
+
font-size: 0.76rem;
|
|
196
|
+
font-weight: 700;
|
|
197
|
+
letter-spacing: 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
h1,
|
|
201
|
+
p {
|
|
202
|
+
margin-top: 0;
|
|
203
|
+
overflow-wrap: anywhere;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
h1 {
|
|
207
|
+
margin-bottom: 8px;
|
|
208
|
+
font-size: 2rem;
|
|
209
|
+
line-height: 1.06;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.subtitle {
|
|
213
|
+
margin-bottom: 0;
|
|
214
|
+
color: var(--muted);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.nav-link,
|
|
218
|
+
button {
|
|
219
|
+
display: inline-flex;
|
|
220
|
+
justify-content: center;
|
|
221
|
+
align-items: center;
|
|
222
|
+
border: 1px solid color-mix(in srgb, var(--accent) 52%, var(--border));
|
|
223
|
+
border-radius: 8px;
|
|
224
|
+
padding: 9px 12px;
|
|
225
|
+
color: var(--text);
|
|
226
|
+
background: color-mix(in srgb, var(--accent) 12%, var(--surface));
|
|
227
|
+
font: inherit;
|
|
228
|
+
font-weight: 700;
|
|
229
|
+
text-decoration: none;
|
|
230
|
+
cursor: pointer;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.nav-link:focus-visible,
|
|
234
|
+
button:focus-visible,
|
|
235
|
+
textarea:focus-visible {
|
|
236
|
+
outline: 2px solid var(--focus);
|
|
237
|
+
outline-offset: 2px;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.safety-strip {
|
|
241
|
+
display: flex;
|
|
242
|
+
flex-wrap: wrap;
|
|
243
|
+
align-items: center;
|
|
244
|
+
gap: 8px;
|
|
245
|
+
padding: 10px 12px;
|
|
246
|
+
border: 1px solid color-mix(in srgb, var(--warning) 45%, var(--border));
|
|
247
|
+
border-radius: 8px;
|
|
248
|
+
background: color-mix(in srgb, var(--warning) 10%, var(--surface-soft));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.safety-strip span {
|
|
252
|
+
border: 1px solid var(--border);
|
|
253
|
+
border-radius: 999px;
|
|
254
|
+
padding: 4px 8px;
|
|
255
|
+
color: var(--warning);
|
|
256
|
+
font-size: 0.78rem;
|
|
257
|
+
font-weight: 700;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.status-grid {
|
|
261
|
+
display: grid;
|
|
262
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
263
|
+
gap: 10px;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.status-grid article {
|
|
267
|
+
padding: 12px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.status-grid strong {
|
|
271
|
+
display: block;
|
|
272
|
+
font-size: 1.2rem;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.capture-form,
|
|
276
|
+
.result-panel {
|
|
277
|
+
padding: 14px;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.capture-form label {
|
|
281
|
+
display: block;
|
|
282
|
+
margin-bottom: 8px;
|
|
283
|
+
font-weight: 700;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
textarea {
|
|
287
|
+
width: 100%;
|
|
288
|
+
min-height: 96px;
|
|
289
|
+
resize: vertical;
|
|
290
|
+
margin-bottom: 10px;
|
|
291
|
+
padding: 10px;
|
|
292
|
+
border: 1px solid var(--border);
|
|
293
|
+
border-radius: 8px;
|
|
294
|
+
color: var(--text);
|
|
295
|
+
background: var(--surface-soft);
|
|
296
|
+
font: inherit;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.help-text,
|
|
300
|
+
.muted,
|
|
301
|
+
.artifact-path {
|
|
302
|
+
color: var(--muted);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.result-list {
|
|
306
|
+
margin: 0;
|
|
307
|
+
padding: 0;
|
|
308
|
+
list-style: none;
|
|
309
|
+
display: grid;
|
|
310
|
+
gap: 10px;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.result-item {
|
|
314
|
+
border: 1px solid var(--border);
|
|
315
|
+
border-radius: 8px;
|
|
316
|
+
padding: 10px;
|
|
317
|
+
background: var(--surface-soft);
|
|
318
|
+
display: grid;
|
|
319
|
+
gap: 6px;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.result-item p {
|
|
323
|
+
margin-bottom: 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.artifact-path {
|
|
327
|
+
font-family: var(--vscode-editor-font-family, ui-monospace, monospace);
|
|
328
|
+
font-size: 0.85rem;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
button:disabled {
|
|
332
|
+
opacity: 0.6;
|
|
333
|
+
cursor: wait;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
@media (max-width: 820px) {
|
|
337
|
+
.operating-workbench {
|
|
338
|
+
grid-template-columns: 1fr;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.app-rail {
|
|
342
|
+
display: flex;
|
|
343
|
+
flex-wrap: wrap;
|
|
344
|
+
gap: 6px;
|
|
345
|
+
align-items: center;
|
|
346
|
+
padding: 10px 12px;
|
|
347
|
+
border-right: 0;
|
|
348
|
+
border-bottom: 1px solid var(--border);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.rail-title {
|
|
352
|
+
flex: 1 0 100%;
|
|
353
|
+
margin: 0;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.rail-link {
|
|
357
|
+
margin-bottom: 0;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.workspace-frame {
|
|
361
|
+
grid-template-columns: 1fr;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.right-inspector {
|
|
365
|
+
border-left: 0;
|
|
366
|
+
border-top: 1px solid var(--border);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.top-row {
|
|
370
|
+
display: grid;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.status-grid {
|
|
374
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@media (max-width: 560px) {
|
|
379
|
+
.workspace-shell {
|
|
380
|
+
padding: 12px;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.workspace-tabs {
|
|
384
|
+
flex-wrap: wrap;
|
|
385
|
+
overflow-x: visible;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.tab {
|
|
389
|
+
white-space: normal;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.status-grid {
|
|
393
|
+
grid-template-columns: 1fr;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
@media (prefers-reduced-motion: reduce) {
|
|
398
|
+
*,
|
|
399
|
+
*::before,
|
|
400
|
+
*::after {
|
|
401
|
+
animation-duration: 0.01ms !important;
|
|
402
|
+
animation-iteration-count: 1 !important;
|
|
403
|
+
scroll-behavior: auto !important;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
@media (forced-colors: active) {
|
|
408
|
+
.top-row,
|
|
409
|
+
.status-grid article,
|
|
410
|
+
.capture-form,
|
|
411
|
+
.result-panel,
|
|
412
|
+
.result-item,
|
|
413
|
+
.safety-strip,
|
|
414
|
+
.app-rail,
|
|
415
|
+
.rail-link,
|
|
416
|
+
.workspace-tabs,
|
|
417
|
+
.tab,
|
|
418
|
+
.right-inspector,
|
|
419
|
+
.bottom-status,
|
|
420
|
+
textarea,
|
|
421
|
+
button,
|
|
422
|
+
.nav-link {
|
|
423
|
+
border-color: CanvasText;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
@@ -180,6 +180,9 @@ export function validateSideReviewDecisionRequest(input = {}) {
|
|
|
180
180
|
const errors = [];
|
|
181
181
|
if (!cardId) errors.push("card_id is required");
|
|
182
182
|
if (!definition) errors.push(`Unsupported side review decision: ${decision || ""}`);
|
|
183
|
+
if (definition && (definition.side_effect !== "review_artifact_only" || definition.creates_decision_artifact !== true)) {
|
|
184
|
+
errors.push(`Side Review decision endpoint only accepts review artifact actions: ${decision}`);
|
|
185
|
+
}
|
|
183
186
|
if (definition?.side_effect === "review_artifact_only" && !firstString(input.reason, input.note, input.reviewer_note)) {
|
|
184
187
|
errors.push("reason is required for review artifact decisions");
|
|
185
188
|
}
|