@chanlerdev/scorel 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +409 -69
- package/dist/index.js +4593 -1751
- package/dist/index.js.map +4 -4
- package/docs/CHANGELOG.md +115 -0
- package/docs/ROADMAP.md +112 -9
- package/docs/SHIP.md +9 -3
- package/docs/spec/channels.md +107 -100
- package/docs/spec/client.md +11 -5
- package/docs/spec/extensions.md +115 -43
- package/docs/spec/ship/S0062-npm-package-and-release-workflow.md +3 -0
- package/docs/spec/ship/S0063-ai-release-notes.md +129 -0
- package/docs/spec/ship/S0064-gui-product-intent-and-boundary.md +79 -0
- package/docs/spec/ship/S0065-gui-electron-shell-and-embedded-host.md +73 -0
- package/docs/spec/ship/S0066-gui-local-project-workspace.md +79 -0
- package/docs/spec/ship/S0067-gui-relay-device-and-remote-project-selection.md +97 -0
- package/docs/spec/ship/S0068-gui-codex-app-polish-and-e2e.md +102 -0
- package/docs/spec/ship/S0068-gui-e2e-verification.md +50 -0
- package/docs/spec/ship/S0069-gui-codex-ui-refactor.md +371 -0
- package/docs/spec/ship/S0070-gui-streaming-and-tool-blocks.md +202 -0
- package/docs/spec/ship/S0071-gui-visual-fidelity-and-settings-shell.md +360 -0
- package/docs/spec/ship/S0072-gui-glass-sidebar-and-picker-anchoring.md +116 -0
- package/docs/spec/ship/S0073-provider-model-profile-contract.md +241 -0
- package/docs/spec/ship/S0074-gui-model-provider-settings-split.md +113 -0
- package/docs/spec/ship/S0075-provider-catalog-model-cards.md +93 -0
- package/docs/spec/ship/S0076-provider-modal-search-and-direct-key.md +70 -0
- package/docs/spec/ship/S0077-auxiliary-session-title-generation.md +95 -0
- package/docs/spec/ship/S0078-gui-provider-settings-forward-config-and-simplification.md +150 -0
- package/docs/spec/ship/S0079-gui-sidebar-layout-controls.md +49 -0
- package/docs/spec/ship/S0080-session-title-hook-and-gui-markdown-dark-code.md +58 -0
- package/docs/spec/ship/S0081-automatic-memory.md +117 -0
- package/docs/spec/ship/S0082-memory-journal-tool-and-idle-dream.md +107 -0
- package/docs/spec/ship/S0083-extension-manifest-and-im-channel-runtime.md +338 -0
- package/docs/spec/ship/S0084-built-in-telegram-im-extension.md +188 -0
- package/docs/spec/ship/S0085-gui-im-extension-settings.md +47 -0
- package/docs/spec/ship/S0086-auto-compact-and-session-memory.md +124 -0
- package/docs/spec/ship/S0087-gui-ui-polish-sweep.md +153 -0
- package/docs/spec/ship/S0088-gui-streaming-thinking-contract.md +35 -0
- package/docs/spec/ship/S0089-memory-reliability-and-dream-trigger.md +84 -0
- package/docs/spec/ship/S0090-gui-provider-delete-and-dark-code-theme.md +77 -0
- package/docs/spec/ship/S0091-built-in-qq-and-wechat-im-extensions.md +125 -0
- package/docs/spec/ship/S0092-im-message-media-and-human-cadence.md +83 -0
- package/docs/spec/ship/S0093-gui-im-settings-platform-layout.md +66 -0
- package/docs/spec/ship/S0094-im-inbound-runtime.md +67 -0
- package/docs/spec/ship/S0095-gui-im-session-list-refresh.md +36 -0
- package/extensions/builtin/loopback/adapter.js +13 -0
- package/extensions/builtin/loopback/scorel.extension.json +7 -0
- package/extensions/builtin/loopback/skills/loopback/SKILL.md +9 -0
- package/extensions/builtin/qq/adapter.d.ts +27 -0
- package/extensions/builtin/qq/adapter.js +384 -0
- package/extensions/builtin/qq/scorel.extension.json +7 -0
- package/extensions/builtin/qq/skills/qq/SKILL.md +9 -0
- package/extensions/builtin/telegram/adapter.d.ts +43 -0
- package/extensions/builtin/telegram/adapter.js +259 -0
- package/extensions/builtin/telegram/scorel.extension.json +7 -0
- package/extensions/builtin/telegram/skills/telegram/SKILL.md +11 -0
- package/extensions/builtin/wechat/adapter.d.ts +24 -0
- package/extensions/builtin/wechat/adapter.js +226 -0
- package/extensions/builtin/wechat/scorel.extension.json +7 -0
- package/extensions/builtin/wechat/skills/wechat/SKILL.md +9 -0
- package/package.json +6 -2
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# S0067: GUI Relay Device And Remote Project Selection
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Add the remote half of the GUI product model: Settings can add Relay Devices, and Add Project can browse a selected Relay Device and add only explicitly chosen remote Projects to the GUI Project list.
|
|
6
|
+
|
|
7
|
+
## Scope
|
|
8
|
+
|
|
9
|
+
- Add Settings UI for Relay Device management.
|
|
10
|
+
- Support the existing Relay pairing / authorization model.
|
|
11
|
+
- Persist GUI Device records and connector metadata locally.
|
|
12
|
+
- Connect to a Relay Device through `RelayTransport` / `DaemonClient`.
|
|
13
|
+
- Add Project flow can choose:
|
|
14
|
+
- Local embedded Host.
|
|
15
|
+
- A configured Relay Device.
|
|
16
|
+
- Browse remote directories through the selected Relay Device Host.
|
|
17
|
+
- Register the selected remote Project on that Host.
|
|
18
|
+
- Persist GUI-visible remote Project selection as `deviceId + projectId`.
|
|
19
|
+
- Show selected remote Projects in the main Project list alongside local Projects.
|
|
20
|
+
- Remote Project list must not automatically include every Project in the remote Host Registry.
|
|
21
|
+
|
|
22
|
+
## Non-Goals
|
|
23
|
+
|
|
24
|
+
- Do not implement SSH Remote Device.
|
|
25
|
+
- Do not implement direct WS + token as a GUI path.
|
|
26
|
+
- Do not add account login or OAuth.
|
|
27
|
+
- Do not move remote Project authority into Relay.
|
|
28
|
+
- Do not let Relay store Project, Session, prompt, tool result, Runtime, JSONL, or replay cache.
|
|
29
|
+
|
|
30
|
+
## Acceptance Criteria
|
|
31
|
+
|
|
32
|
+
- Settings can add a Relay Device using the official Relay default unless overridden for development.
|
|
33
|
+
- GUI can connect to the configured Relay Device and show connection status.
|
|
34
|
+
- Add Project can browse directories on the selected Relay Device.
|
|
35
|
+
- Registering a remote Project stores the returned `projectId` and marks it visible in GUI.
|
|
36
|
+
- The main Project list shows only GUI-selected remote Projects for that Relay Device.
|
|
37
|
+
- Removing or hiding a GUI-selected remote Project does not delete the remote Host Project unless a later spec explicitly adds destructive management.
|
|
38
|
+
- Local Projects remain all-visible.
|
|
39
|
+
|
|
40
|
+
## Test Requirements
|
|
41
|
+
|
|
42
|
+
Run:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pnpm --filter @scorel/app-gui build
|
|
46
|
+
pnpm --filter @scorel/app-gui typecheck
|
|
47
|
+
pnpm --filter @scorel/app-gui test
|
|
48
|
+
pnpm typecheck
|
|
49
|
+
pnpm test
|
|
50
|
+
git diff --check
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Manual smoke must use:
|
|
54
|
+
|
|
55
|
+
- real Relay service or local Relay process
|
|
56
|
+
- real Host connected outbound to Relay
|
|
57
|
+
- real Relay Device configuration in GUI
|
|
58
|
+
- real remote directory browsing through Relay
|
|
59
|
+
- real remote Project registration
|
|
60
|
+
|
|
61
|
+
## Implementation Notes
|
|
62
|
+
|
|
63
|
+
- GUI Settings creates Relay pair codes as the entry client `client_gui`.
|
|
64
|
+
- The existing Host-side pairing path remains authoritative: the remote Host still runs `scorel pair <pair-code>` or the same `redeem_pair` flow to authorize the GUI client.
|
|
65
|
+
- GUI Relay Device records and GUI-visible remote Project selections are stored locally in `~/.scorel/gui/gui-store.json`.
|
|
66
|
+
- Remote Project visibility is explicit:
|
|
67
|
+
- local Projects are listed from the embedded local Host Registry.
|
|
68
|
+
- remote Projects are listed only from GUI-selected `deviceId + projectId` records.
|
|
69
|
+
- `list_projects` from the remote Host is not used as the GUI main Project list.
|
|
70
|
+
- Relay connections are owned by Electron main process through `RelayTransport` and `DaemonClient`; renderer only receives sanitized device/project/session data through IPC.
|
|
71
|
+
|
|
72
|
+
## Verification
|
|
73
|
+
|
|
74
|
+
- `pnpm --filter @scorel/app-gui typecheck` passed.
|
|
75
|
+
- `pnpm --filter @scorel/app-gui test` passed.
|
|
76
|
+
- `pnpm --filter @scorel/app-gui build` passed.
|
|
77
|
+
- `pnpm typecheck` passed.
|
|
78
|
+
- `pnpm test` passed.
|
|
79
|
+
- `pnpm pack:smoke` passed.
|
|
80
|
+
- `git diff --check` passed.
|
|
81
|
+
- `pnpm gui` loaded Electron without a main/renderer load error and exited cleanly after SIGTERM.
|
|
82
|
+
- Focused Relay tests use a real local Relay server, a real `HostRelayClient`, real remote directory browsing, and real remote Project registration.
|
|
83
|
+
- Full visual Relay e2e is intentionally left to S0068, which owns Codex App polish and local + Relay end-to-end verification.
|
|
84
|
+
|
|
85
|
+
## Affected Paths
|
|
86
|
+
|
|
87
|
+
- `apps/gui/**`
|
|
88
|
+
- `packages/client/**`
|
|
89
|
+
- `packages/protocol/**` only if GUI-visible selection requires shared types
|
|
90
|
+
- `docs/ROADMAP.md`
|
|
91
|
+
- `docs/spec/ship/S0067-gui-relay-device-and-remote-project-selection.md`
|
|
92
|
+
|
|
93
|
+
## Risks
|
|
94
|
+
|
|
95
|
+
- Remote Project visibility can accidentally collapse back to WebUI behavior. The invariant is: remote Host Registry is not the GUI main Project list.
|
|
96
|
+
- Pairing and Device persistence can leak secrets. Do not log Relay tokens or full connector secrets.
|
|
97
|
+
- Relay may be mistaken for a cloud backend. It remains only proxy + authorization registry.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# S0068: GUI Codex App Polish And E2E
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Bring the first GUI milestone to a credible Codex App-style desktop experience and verify both local and Relay Project paths end to end with real product resources.
|
|
6
|
+
|
|
7
|
+
## Scope
|
|
8
|
+
|
|
9
|
+
- Polish the GUI visual system and interaction model against Codex App expectations:
|
|
10
|
+
- quiet workbench layout
|
|
11
|
+
- dense but readable Project / Session navigation
|
|
12
|
+
- restrained controls
|
|
13
|
+
- clear connection and running states
|
|
14
|
+
- no marketing-style landing page
|
|
15
|
+
- Align reusable styling with `docs/design.md` where it still applies.
|
|
16
|
+
- Add explicit empty, loading, offline, disconnected, and error states.
|
|
17
|
+
- Validate local Project path end to end.
|
|
18
|
+
- Validate Relay Project path end to end.
|
|
19
|
+
- Document verification evidence.
|
|
20
|
+
- Mark M9 Done when all prior M9 specs are complete and verified.
|
|
21
|
+
|
|
22
|
+
## Non-Goals
|
|
23
|
+
|
|
24
|
+
- Do not add SSH Remote Device.
|
|
25
|
+
- Do not add HTTP API.
|
|
26
|
+
- Do not add plugin / automation / file explorer / diff viewer unless already required by a prior M9 spec.
|
|
27
|
+
- Do not ship production desktop installers unless a dedicated release spec is created.
|
|
28
|
+
|
|
29
|
+
## Acceptance Criteria
|
|
30
|
+
|
|
31
|
+
- GUI feels like a desktop workbench, not a browser page wrapped in a window.
|
|
32
|
+
- Local Project: add/select Project, create Session, send prompt, see persisted response.
|
|
33
|
+
- Relay Project: add Relay Device, select remote Project, create/open Session, send prompt, see persisted response.
|
|
34
|
+
- Offline Relay Device and disconnected Host states are visible and recoverable without app crash.
|
|
35
|
+
- Text fits in project/session rows and composer controls across expected desktop window sizes.
|
|
36
|
+
- M9 ROADMAP status can be moved to Done only after this spec and earlier M9 specs pass verification.
|
|
37
|
+
|
|
38
|
+
## Test Requirements
|
|
39
|
+
|
|
40
|
+
Run:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpm --filter @scorel/app-gui build
|
|
44
|
+
pnpm --filter @scorel/app-gui typecheck
|
|
45
|
+
pnpm --filter @scorel/app-gui test
|
|
46
|
+
pnpm typecheck
|
|
47
|
+
pnpm test
|
|
48
|
+
pnpm pack:smoke
|
|
49
|
+
pnpm verify:m9-gui
|
|
50
|
+
git diff --check
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Manual e2e must use:
|
|
54
|
+
|
|
55
|
+
- real GUI app
|
|
56
|
+
- real embedded local Host
|
|
57
|
+
- real Relay path
|
|
58
|
+
- real JSONL sessions
|
|
59
|
+
- real provider
|
|
60
|
+
|
|
61
|
+
If browser or screenshot tooling is used for renderer verification, record viewport/window sizes and include screenshots or notes in the verification artifact.
|
|
62
|
+
|
|
63
|
+
## Affected Paths
|
|
64
|
+
|
|
65
|
+
- `apps/gui/**`
|
|
66
|
+
- `docs/design.md` only if GUI-specific design rules need to be added
|
|
67
|
+
- `docs/ROADMAP.md`
|
|
68
|
+
- `docs/spec/ship/S0068-gui-codex-app-polish-and-e2e.md`
|
|
69
|
+
- optional verification artifact under `docs/spec/ship/`
|
|
70
|
+
|
|
71
|
+
## Implementation Notes
|
|
72
|
+
|
|
73
|
+
- The GUI uses the `docs/design.md` token set directly in `apps/gui/src/index.html`.
|
|
74
|
+
- Renderer states now distinguish:
|
|
75
|
+
- no Project
|
|
76
|
+
- Project without Session
|
|
77
|
+
- busy/loading
|
|
78
|
+
- local Host error
|
|
79
|
+
- Relay Device online/offline
|
|
80
|
+
- Relay Device absent before remote browse
|
|
81
|
+
- Composer and Project/Session rows use fixed dimensions, text truncation, focus states, and restrained status chips.
|
|
82
|
+
- The S0067 product model is unchanged: local Projects remain all-visible; remote Projects are still shown only after GUI selection.
|
|
83
|
+
|
|
84
|
+
## Verification
|
|
85
|
+
|
|
86
|
+
See [`S0068-gui-e2e-verification.md`](S0068-gui-e2e-verification.md).
|
|
87
|
+
|
|
88
|
+
Current status:
|
|
89
|
+
|
|
90
|
+
- GUI polish: complete.
|
|
91
|
+
- Automated local product-path verification: complete.
|
|
92
|
+
- Automated Relay product-path verification: complete.
|
|
93
|
+
- Electron launch smoke: complete.
|
|
94
|
+
- Real-provider GUI e2e: complete.
|
|
95
|
+
|
|
96
|
+
M9 can be marked Done because all S0064-S0068 specs are complete and `pnpm verify:m9-gui` passed with provider credentials.
|
|
97
|
+
|
|
98
|
+
## Risks
|
|
99
|
+
|
|
100
|
+
- Codex App parity can become an unbounded visual target. S0068 should polish the first M9 workflow, not invent every Codex feature.
|
|
101
|
+
- E2E can pass locally while desktop packaging is still unsolved. Packaging is separate unless explicitly added.
|
|
102
|
+
- UI reuse from WebUI can carry browser-specific assumptions. GUI should reuse implementation only when it supports the desktop product model.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# S0068 GUI E2E Verification
|
|
2
|
+
|
|
3
|
+
Date: 2026-06-08
|
|
4
|
+
|
|
5
|
+
## Automated Product-Path Evidence
|
|
6
|
+
|
|
7
|
+
- `pnpm --filter @scorel/app-gui typecheck` passed.
|
|
8
|
+
- `pnpm --filter @scorel/app-gui test` passed.
|
|
9
|
+
- `pnpm --filter @scorel/app-gui build` passed.
|
|
10
|
+
- GUI local Host tests cover Project registration, Session creation, prompt send, persisted assistant response, and session-scoped transcript filtering.
|
|
11
|
+
- GUI Relay tests cover a real local Relay server, real `HostRelayClient`, authorized Relay Device discovery, remote directory browsing, explicit remote Project selection, remote Session creation, prompt send through Relay, and persisted assistant response.
|
|
12
|
+
|
|
13
|
+
Automated prompt tests use a deterministic test provider so CI stays stable. They still use the real Host, Relay, `DaemonClient`, JSONL, and Project registry path.
|
|
14
|
+
|
|
15
|
+
## Desktop Smoke
|
|
16
|
+
|
|
17
|
+
- `pnpm gui` launched Electron without main-process or renderer load errors.
|
|
18
|
+
- Electron exited cleanly after SIGTERM.
|
|
19
|
+
- Screenshot capture was attempted at desktop size, but the macOS automation permission dialog covered the app window. The screenshot was not committed because it contained unrelated desktop/browser content.
|
|
20
|
+
|
|
21
|
+
## Real Provider Smoke
|
|
22
|
+
|
|
23
|
+
Formal verifier:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm verify:m9-gui
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The verifier covers:
|
|
30
|
+
|
|
31
|
+
- real provider config loaded through `loadScorelConfig`.
|
|
32
|
+
- GUI embedded local Host service.
|
|
33
|
+
- local Project registration, Session creation, prompt send, and persisted assistant response.
|
|
34
|
+
- local Relay server.
|
|
35
|
+
- GUI Relay pair session creation.
|
|
36
|
+
- Host-side Relay pair redemption.
|
|
37
|
+
- remote Host outbound Relay connection through `HostRelayClient`.
|
|
38
|
+
- GUI Relay Device discovery.
|
|
39
|
+
- explicit remote Project selection.
|
|
40
|
+
- remote Session creation, prompt send through Relay, and persisted assistant response.
|
|
41
|
+
|
|
42
|
+
Result:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
ok: true
|
|
46
|
+
local projectId=prj_109119eb-9401-4d62-ba46-052e1a128fae sessionId=ses_62d6865d-67d7-4f77-9e27-375946ae4582 eventCount=5
|
|
47
|
+
relay deviceId=device_gui_m9_remote projectId=prj_d57f2c62-44dc-42ab-b21c-0fe151053109 sessionId=ses_d85f7028-daa9-406a-bfd7-77f159d0d031 eventCount=7
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
This proves the real-provider GUI prompt smoke completed for both the embedded local Host path and the Relay remote Project path. The verification command did not persist provider credentials into the repository.
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# S0069: GUI Codex App UI Refactor (Skeleton + Markdown + Tool Block Registry)
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Rebuild the M9 GUI renderer to match Codex App reference screenshots (image2-6): three-segment sidebar with project-inline sessions, pill composer, project picker pill with overlay, Add Remote Project modal, dedicated Settings view. Establish the foundation for Markdown rendering and an event-driven tool-block component registry. Add a streaming IPC channel so future streaming UX (S0070) does not require another IPC reshape.
|
|
6
|
+
|
|
7
|
+
This spec replaces the single-file `apps/gui/src/renderer.tsx` (559 lines, character-icon, two-list sidebar, embedded settings popover) with a modular renderer tree under `apps/gui/src/renderer/`.
|
|
8
|
+
|
|
9
|
+
Source of truth for product model is M9 (`S0064`–`S0068`); this spec is M9 follow-up polish, not a product re-scoping.
|
|
10
|
+
|
|
11
|
+
Reference screenshots:
|
|
12
|
+
|
|
13
|
+
- image2 — populated session view with topbar title, action chips, review banner.
|
|
14
|
+
- image3 — empty state with large H1, pill composer, project picker mini pill below.
|
|
15
|
+
- image4 — project picker overlay: search, list, "add local / add remote / no project".
|
|
16
|
+
- image5 — Add Remote Project modal: host dropdown, path input + reset, directory list, cancel / add.
|
|
17
|
+
- image6 — sidebar with each project inline-expanding its own sessions.
|
|
18
|
+
|
|
19
|
+
User-explicit deletions from the Codex reference (do **not** ship):
|
|
20
|
+
|
|
21
|
+
- empty-state "connect messaging / email / files" plugin recommendation cards.
|
|
22
|
+
- bottom-of-sidebar global "对话" history group.
|
|
23
|
+
- in-composer review banner (`2 个文件已更改 +95 -1` / `审查`).
|
|
24
|
+
- "不使用项目" (null project / workspace mode) — GUI is Project-first per `S0064`.
|
|
25
|
+
- real model-picker / mic / "完全访问" toggling — all stay disabled placeholders (Codex semantic).
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Scope
|
|
30
|
+
|
|
31
|
+
### 1. Renderer tree
|
|
32
|
+
|
|
33
|
+
Replace `apps/gui/src/renderer.tsx` and the inline `<style>` block in `apps/gui/src/index.html` with:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
apps/gui/src/renderer/
|
|
37
|
+
├── main.tsx # ReactDOM root
|
|
38
|
+
├── App.tsx # view-mode router (workspace | settings)
|
|
39
|
+
├── styles.css # tokens + base + layout, replaces 553-line inline style
|
|
40
|
+
├── tokens.ts # token names exported for component literals
|
|
41
|
+
├── shell/
|
|
42
|
+
│ ├── Sidebar.tsx
|
|
43
|
+
│ ├── ProjectTree.tsx # one project row + inline session list + collapse
|
|
44
|
+
│ ├── SidebarActionRow.tsx # `+ 新对话` / 搜索 / 插件 / 自动化
|
|
45
|
+
│ └── use-collapsed.ts # localStorage["scorel.gui.collapsed"]: { [key]: boolean }
|
|
46
|
+
├── workspace/
|
|
47
|
+
│ ├── Workspace.tsx # empty | session
|
|
48
|
+
│ ├── EmptyState.tsx # H1 + composer + project picker pill
|
|
49
|
+
│ ├── SessionView.tsx # transcript + composer
|
|
50
|
+
│ └── Topbar.tsx # right-only stack icon (no title bar)
|
|
51
|
+
├── composer/
|
|
52
|
+
│ ├── Composer.tsx
|
|
53
|
+
│ ├── ProjectPickerPill.tsx
|
|
54
|
+
│ ├── ProjectPickerMenu.tsx # overlay: search + list + add local / add remote
|
|
55
|
+
│ └── AddRemoteProjectDialog.tsx # image5 modal
|
|
56
|
+
├── chatbox/
|
|
57
|
+
│ ├── Transcript.tsx
|
|
58
|
+
│ ├── projector.ts # copy of apps/webui/lib/events/projector.ts (independent)
|
|
59
|
+
│ ├── delta-batch.ts # copy (S0070 will activate)
|
|
60
|
+
│ ├── TurnUser.tsx
|
|
61
|
+
│ ├── TurnAssistant.tsx
|
|
62
|
+
│ ├── TurnHarness.tsx
|
|
63
|
+
│ ├── Markdown.tsx # react-markdown + remark-gfm + rehype-sanitize
|
|
64
|
+
│ ├── ShikiCodeBlock.tsx # lazy shiki, github-light-default
|
|
65
|
+
│ └── tool-blocks/
|
|
66
|
+
│ ├── registry.ts
|
|
67
|
+
│ ├── ToolBlock.tsx # routes (call, result?) → registry hit or DefaultJsonBlock
|
|
68
|
+
│ └── DefaultJsonBlock.tsx
|
|
69
|
+
├── settings/
|
|
70
|
+
│ ├── SettingsPage.tsx
|
|
71
|
+
│ └── RelayDevicesPanel.tsx
|
|
72
|
+
└── icons/
|
|
73
|
+
└── index.ts # lucide-react re-exports (Plus, Search, Puzzle, Clock,
|
|
74
|
+
# Settings, Folder, ChevronRight, ChevronDown, X, Mic,
|
|
75
|
+
# ArrowUp, Square, Globe, RotateCcw, Check)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Renderer entry moves to `apps/gui/src/renderer/main.tsx`. Old `apps/gui/src/renderer.tsx` deleted.
|
|
79
|
+
|
|
80
|
+
### 2. Design tokens
|
|
81
|
+
|
|
82
|
+
Token values copied verbatim from `docs/design.md` §2 (locked palette, sans-only fonts, five-step type ladder, spacing, radii, shadow disabled). Tokens defined in `:root` of `styles.css`. Dark-mode placeholder via `@media (prefers-color-scheme: dark)` block kept commented out.
|
|
83
|
+
|
|
84
|
+
### 3. Sidebar — three-segment, project inline sessions
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
┌─ 280px Sidebar (--color-surface) ──────────┐
|
|
88
|
+
│ + 新对话 [active route /] │
|
|
89
|
+
│ 🔍 搜索 [disabled] │
|
|
90
|
+
│ 🧩 插件 [disabled] │
|
|
91
|
+
│ 🤖 自动化 [disabled] │
|
|
92
|
+
│ │
|
|
93
|
+
│ PROJECTS (12px caps) │
|
|
94
|
+
│ │
|
|
95
|
+
│ ▾ workspace │
|
|
96
|
+
│ 梳理对比分析框架 ⌘1 │
|
|
97
|
+
│ ▸ warp │
|
|
98
|
+
│ ▸ Scorel │
|
|
99
|
+
│ ▸ Scorel Chanler... ●online │
|
|
100
|
+
│ ▸ Tickel │
|
|
101
|
+
│ ▸ docx-pure │
|
|
102
|
+
│ ... │
|
|
103
|
+
│ │
|
|
104
|
+
│ ⚙ 设置 (mobile icon) │
|
|
105
|
+
└────────────────────────────────────────────┘
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- Project row click = toggle collapse, **not** navigate. Active project derives from active session route only.
|
|
109
|
+
- Session row click = open session in workspace.
|
|
110
|
+
- Collapse persists to `localStorage["scorel.gui.collapsed"]`.
|
|
111
|
+
- `+ 新对话` row routes to workspace empty state with current project preselected.
|
|
112
|
+
- No `+` button next to "PROJECTS" header in this segment — add-project entrypoint is also exposed as a row at the bottom of the project picker overlay (sidebar `+` is dual-entry per user decision: keep one `+` button at the projects header for sidebar discoverability).
|
|
113
|
+
- No "对话" global history group. No theme toggle row.
|
|
114
|
+
|
|
115
|
+
### 4. Composer
|
|
116
|
+
|
|
117
|
+
Pill, 24px radius, 1px `--color-border`, no shadow.
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
┌──────────────────────────────────────────────────┐
|
|
121
|
+
│ 随心输入 │
|
|
122
|
+
│ │
|
|
123
|
+
│ ⊕ ⚠ 完全访问 ▾ 5.5 超高 ▾ 🎤 ●│
|
|
124
|
+
└──────────────────────────────────────────────────┘
|
|
125
|
+
▱ workspace ▾ ← project picker pill, BELOW composer
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- `⚠ 完全访问 ▾` rendered with `--color-status-warn`. Disabled placeholder.
|
|
129
|
+
- `5.5 超高 ▾` rendered as plain pill. Disabled placeholder.
|
|
130
|
+
- `⊕` and `🎤` disabled placeholders.
|
|
131
|
+
- Send button: 32×32 circle, `--color-accent` background, `↑` glyph. While `inFlight`, swaps to red square (`--color-status-err`) acting as cancel.
|
|
132
|
+
- `meta-row` removed from composer shell. Project picker pill is a sibling component below composer.
|
|
133
|
+
|
|
134
|
+
### 5. Project picker pill + overlay
|
|
135
|
+
|
|
136
|
+
Pill: `▱ {project.displayName} ▾` rendered below composer. Click opens an overlay positioned beneath the pill (image4):
|
|
137
|
+
|
|
138
|
+
- Top: search input `🔍 搜索项目` (filters list).
|
|
139
|
+
- Middle: project list, each row `▱ name`, current selection trailing `✓`.
|
|
140
|
+
- Divider.
|
|
141
|
+
- `▱+ 添加本地项目 ›` → triggers `window.scorel.addLocalProject()` (existing IPC).
|
|
142
|
+
- `🌐 添加远程项目` → opens `AddRemoteProjectDialog`.
|
|
143
|
+
- "不使用项目" row **omitted** (user decision: GUI is Project-first).
|
|
144
|
+
|
|
145
|
+
### 6. Add Remote Project modal
|
|
146
|
+
|
|
147
|
+
`AddRemoteProjectDialog.tsx` reproduces image5:
|
|
148
|
+
|
|
149
|
+
- Title `添加远程项目` + close (×).
|
|
150
|
+
- Subtitle `选择已连接的远程主机,并输入此项目的文件夹。`
|
|
151
|
+
- `远程主机` dropdown sourced from `state.relayDevices`.
|
|
152
|
+
- `文件夹路径` input + reset (`RotateCcw`) button.
|
|
153
|
+
- Directory listing (entries from `window.scorel.listRemoteDirectories(deviceId, path)`), click = navigate into.
|
|
154
|
+
- Footer hint `此远程文件夹将作为单独项目显示在侧边栏中。`
|
|
155
|
+
- `取消` / `添加项目` buttons. `添加项目` calls `window.scorel.addRemoteProject(deviceId, workDir)`.
|
|
156
|
+
|
|
157
|
+
### 7. Settings independent view
|
|
158
|
+
|
|
159
|
+
In-renderer view-mode switch via `useState<"workspace" | "settings">("workspace")`. Sidebar `⚙ 设置` row sets mode to `"settings"`. Settings header has a back button (`ChevronLeft` + `返回`) restoring `"workspace"`. **No `react-router`**.
|
|
160
|
+
|
|
161
|
+
`SettingsPage.tsx` sections (S0069 ships only Relay Devices; future settings sections are future specs):
|
|
162
|
+
|
|
163
|
+
- Relay Devices: relay URL input, `Pair` button, `Refresh` button, displayed pair code on success, device list with online/offline pills. Functionality 1:1 from current `SettingsPopover` in `apps/gui/src/renderer.tsx:476-555`.
|
|
164
|
+
|
|
165
|
+
The Relay popover at the sidebar bottom is removed.
|
|
166
|
+
|
|
167
|
+
### 8. Markdown pipeline
|
|
168
|
+
|
|
169
|
+
`Markdown.tsx`:
|
|
170
|
+
|
|
171
|
+
- `react-markdown` 10.x with `remark-gfm` 4.x and `rehype-sanitize` 6.x.
|
|
172
|
+
- Sanitize schema duplicates the allowlist in `apps/webui/components/chatbox/markdown-view.tsx` (allow `className` attribute, allow code-block class for highlighter).
|
|
173
|
+
- Code blocks dispatch to `ShikiCodeBlock.tsx`. Shiki 4.x lazy `createHighlighterCore` shared across renders, language loaded on demand, theme `github-light-default`. Fallback to plain `<pre><code>` while loading.
|
|
174
|
+
|
|
175
|
+
GUI-only adjustments vs webui copy: drop Tailwind class names; use plain CSS class names defined in `styles.css`.
|
|
176
|
+
|
|
177
|
+
### 9. Tool-block registry (event-driven, generic)
|
|
178
|
+
|
|
179
|
+
`tool-blocks/registry.ts`:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import type {
|
|
183
|
+
ToolCallContentBlock,
|
|
184
|
+
ToolResultContentBlock,
|
|
185
|
+
} from "@scorel/protocol";
|
|
186
|
+
|
|
187
|
+
export type ToolBlockProps = {
|
|
188
|
+
call: ToolCallContentBlock; // packages/protocol/src/messages.ts:13
|
|
189
|
+
result?: ToolResultContentBlock; // packages/protocol/src/messages.ts:21
|
|
190
|
+
pending: boolean; // result not yet arrived
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export type ToolBlockComponent = React.ComponentType<ToolBlockProps>;
|
|
194
|
+
|
|
195
|
+
const registry = new Map<string, ToolBlockComponent>();
|
|
196
|
+
export function registerToolBlock(toolName: string, component: ToolBlockComponent): void;
|
|
197
|
+
export function lookupToolBlock(toolName: string): ToolBlockComponent;
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`ToolBlock.tsx` looks up by `call.toolName`; falls back to `DefaultJsonBlock` (renders `tool_call.args` and `tool_result.result` as JSON fences via `Markdown.tsx`).
|
|
201
|
+
|
|
202
|
+
`Transcript.tsx` projects `PersistentEvent[]` via `projector.ts`. The projector pairs `tool_call` content blocks with their `tool_result` blocks by `toolCallId`. Transcript renders each pair through `<ToolBlock />`.
|
|
203
|
+
|
|
204
|
+
S0069 ships **only** `DefaultJsonBlock`. Specialized blocks (Read / Glob / Grep / Edit / Write / Bash / TodoWrite) are S0070.
|
|
205
|
+
|
|
206
|
+
Adding a new tool renderer in any future spec is one line: `registerToolBlock("MyTool", MyToolBlock)` in module init. Main rendering path unchanged.
|
|
207
|
+
|
|
208
|
+
### 10. Streaming IPC channel (forward-compatible)
|
|
209
|
+
|
|
210
|
+
Add channels and types so S0070 can light up streaming without another IPC pass:
|
|
211
|
+
|
|
212
|
+
`apps/gui/src/shared/ipc.ts`:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
export type GuiSessionEventPayload = {
|
|
216
|
+
sessionId: SessionId;
|
|
217
|
+
event: ScorelEvent;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export const guiIpcChannels = {
|
|
221
|
+
...,
|
|
222
|
+
attachSession: "scorel:attachSession", // (project, sessionId) → void
|
|
223
|
+
detachSession: "scorel:detachSession", // (sessionId) → void
|
|
224
|
+
sessionEvent: "scorel:sessionEvent", // main → renderer push
|
|
225
|
+
} as const;
|
|
226
|
+
|
|
227
|
+
export type GuiApi = {
|
|
228
|
+
...,
|
|
229
|
+
attachSession(project: GuiProjectRef, sessionId: SessionId): Promise<void>;
|
|
230
|
+
detachSession(sessionId: SessionId): Promise<void>;
|
|
231
|
+
onSessionEvent(handler: (payload: GuiSessionEventPayload) => void): () => void;
|
|
232
|
+
};
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`apps/gui/src/main.ts` keeps a `BrowserWindow` reference. On `attachSession`, look up the relevant `DaemonClient` and call `client.subscribe((event) => webContents.send("scorel:sessionEvent", { sessionId, event }))`; remember the unsubscribe by `sessionId`. On `detachSession`, run the saved unsubscribe.
|
|
236
|
+
|
|
237
|
+
`local-host.ts` and `relay-service.ts` expose internal `getClient()` / `subscribe(sessionId, handler)` helpers; no domain logic moves there.
|
|
238
|
+
|
|
239
|
+
`sendMessage` semantics narrow: it **starts** the prompt and resolves with `{ accepted: true }` once the daemon accepts. Live events arrive via the channel push. Renderer must `attachSession` before calling `sendMessage` (Transcript mounts → attach → send → receive events → unmount → detach).
|
|
240
|
+
|
|
241
|
+
Renderer behavior in S0069: attach + receive `PersistentEvent` snapshots and push them through the projector each frame (no batching, no cursor). Transient events (`text_delta` etc.) are ingested by the projector but not visualized — S0070 lights them up.
|
|
242
|
+
|
|
243
|
+
### 11. esbuild build script
|
|
244
|
+
|
|
245
|
+
`apps/gui/scripts/build.mjs`:
|
|
246
|
+
|
|
247
|
+
- entry `src/renderer/main.tsx` (was `src/renderer.tsx`).
|
|
248
|
+
- add CSS loader: import `./styles.css` from `main.tsx`, esbuild bundles it to `.dist/renderer.css`. `index.html` adds `<link rel="stylesheet" href="renderer.css">`.
|
|
249
|
+
|
|
250
|
+
### 12. package.json
|
|
251
|
+
|
|
252
|
+
Add to `apps/gui/package.json` dependencies:
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
"lucide-react": "^0.488.0"
|
|
256
|
+
"react-markdown": "^10.1.0"
|
|
257
|
+
"remark-gfm": "^4.0.1"
|
|
258
|
+
"rehype-sanitize": "^6.0.0"
|
|
259
|
+
"shiki": "^4.1.0"
|
|
260
|
+
"@shikijs/rehype": "^4.1.0"
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Versions match `apps/webui/package.json`.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Non-Goals
|
|
268
|
+
|
|
269
|
+
- No streaming cursor, RAF batcher, IntersectionObserver autoscroll, jump-to-bottom button. (S0070.)
|
|
270
|
+
- No specialized tool blocks (Read / Glob / Grep / Edit / Write / Bash / TodoWrite). All routes go through `DefaultJsonBlock`. (S0070.)
|
|
271
|
+
- No diff viewer. (S0070.)
|
|
272
|
+
- No model picker / 🎤 / `完全访问` real toggling. Disabled placeholders only.
|
|
273
|
+
- No dark-mode implementation (placeholder block only).
|
|
274
|
+
- No `react-router`. View-mode switch is local React state.
|
|
275
|
+
- No WebUI changes. Reverse-reuse of GUI components in WebUI is a future product direction, not a S0069 deliverable.
|
|
276
|
+
- No new Electron menu, no auto-update, no installer packaging.
|
|
277
|
+
- No new Host / Daemon / Relay / Protocol package change beyond the additive IPC channels.
|
|
278
|
+
- No removal of any existing IPC handler. `sendMessage` keeps responding (now with `{ accepted: true }` ack), so any callers staying on the old polling shape (none in this repo) would still build.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Acceptance Criteria
|
|
283
|
+
|
|
284
|
+
- `apps/gui/src/renderer.tsx` deleted; `apps/gui/src/renderer/main.tsx` is the entry.
|
|
285
|
+
- `apps/gui/src/index.html` has no `<style>` block; layout/colors come from `renderer.css`.
|
|
286
|
+
- Empty state matches image3: H1 reads `我们应该在 {project.displayName} 中做些什么?` (project-aware, falls back to `我们应该构建什么?` when no project resolves), pill composer, project picker pill below, no recommendation cards, no topbar title.
|
|
287
|
+
- Project picker overlay matches image4: search box, list with current `✓`, `添加本地项目` and `添加远程项目` rows, no "不使用项目".
|
|
288
|
+
- Add Remote Project modal matches image5: host dropdown, path input + reset, directory listing, footer hint, cancel / add buttons.
|
|
289
|
+
- Sidebar matches image6: 4 fixed action rows on top, project list with each project's sessions inline-expanded under it; collapse persists.
|
|
290
|
+
- Sidebar bottom shows only `⚙ 设置` (mobile icon kept). No theme toggle row, no "对话" history group.
|
|
291
|
+
- Settings is an in-renderer view: `⚙ 设置` switches to settings, back button restores workspace. Relay Devices section reproduces existing pair / refresh / device-list functionality.
|
|
292
|
+
- Markdown rendering visible in transcript: GFM tables, code blocks with Shiki highlight (lazy), sanitized HTML (no script execution).
|
|
293
|
+
- Tool calls render via `ToolBlock`: with `DefaultJsonBlock`, both `tool_call.args` and `tool_result.result` shown as fenced JSON. `ReadBlock` etc. registered as `DefaultJsonBlock` (visual identity), confirming the registry path is wired.
|
|
294
|
+
- IPC channels `attachSession` / `detachSession` / `sessionEvent` exposed via `window.scorel`, exercised by `Transcript` mount/unmount.
|
|
295
|
+
- `sendMessage` returns `{ accepted: true }` shape; renderer no longer awaits a `PersistentEvent[]` from it.
|
|
296
|
+
- All existing existing local + Relay project flows continue to function: add local project (via picker), add Relay device (via Settings), add remote project (via picker → modal), open session, send prompt, receive response.
|
|
297
|
+
- Lucide icons used everywhere they appear in screenshots; no Unicode-character icons remain in the renderer source.
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Test Requirements
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
pnpm --filter @scorel/app-gui build
|
|
305
|
+
pnpm --filter @scorel/app-gui typecheck
|
|
306
|
+
pnpm --filter @scorel/app-gui test
|
|
307
|
+
pnpm typecheck
|
|
308
|
+
pnpm test
|
|
309
|
+
pnpm pack:smoke
|
|
310
|
+
git diff --check
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Manual e2e:
|
|
314
|
+
|
|
315
|
+
- `pnpm --filter @scorel/app-gui dev` launches Electron.
|
|
316
|
+
- Visual diff against image2-6 for sidebar, empty state, picker overlay, add-remote modal, settings.
|
|
317
|
+
- Real provider single-prompt smoke: open existing local project, create new session, send a prompt that triggers at least one tool call, see `DefaultJsonBlock` render the tool_call/tool_result pair, see assistant text rendered as Markdown.
|
|
318
|
+
- Settings flow: pair a Relay device, refresh, add remote project via picker → modal, send prompt against remote, confirm response.
|
|
319
|
+
|
|
320
|
+
Existing automated suites under `apps/gui/src/main/*.test.ts` and any `verify:m9-gui` pipeline continue to pass.
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Affected Paths
|
|
325
|
+
|
|
326
|
+
- `apps/gui/src/renderer.tsx` — deleted.
|
|
327
|
+
- `apps/gui/src/renderer/**` — new tree.
|
|
328
|
+
- `apps/gui/src/index.html` — strip inline `<style>`, add stylesheet link, point script at `renderer.js` (unchanged build artifact name).
|
|
329
|
+
- `apps/gui/src/main.ts` — add `BrowserWindow` reference for `webContents.send`, attach/detach handlers, narrowed `sendMessage` ack.
|
|
330
|
+
- `apps/gui/src/preload.ts` — expose new channels.
|
|
331
|
+
- `apps/gui/src/shared/ipc.ts` — channels, types, narrowed `GuiApi.sendMessage` return type.
|
|
332
|
+
- `apps/gui/src/main/local-host.ts` — `subscribe(sessionId, handler)` helper; `sendLocalMessage` no longer awaits all events.
|
|
333
|
+
- `apps/gui/src/main/relay-service.ts` — same shape.
|
|
334
|
+
- `apps/gui/scripts/build.mjs` — entry rename + CSS loader.
|
|
335
|
+
- `apps/gui/package.json` — new deps.
|
|
336
|
+
- `docs/ROADMAP.md` — append M9 Follow-up section listing S0069 / S0070.
|
|
337
|
+
- `docs/spec/ship/S0069-gui-codex-ui-refactor.md` — this file.
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Implementation Notes
|
|
342
|
+
|
|
343
|
+
- `projector.ts` and `delta-batch.ts` are copied from `apps/webui/lib/events/` verbatim, then the Tailwind / Next-specific assumptions are stripped. Follow-up product direction (rebasing WebUI onto GUI components) is out of scope here.
|
|
344
|
+
- `Markdown.tsx` sanitize schema mirrors `apps/webui/components/chatbox/markdown-view.tsx` to reuse the audit done there. Any deviation is recorded in this spec.
|
|
345
|
+
- Tool block registry registration happens in `apps/gui/src/renderer/chatbox/tool-blocks/registry.ts` module init: `registerToolBlock("Read", DefaultJsonBlock)` etc. for now. S0070 swaps the right-hand side to specialized components without touching `Transcript.tsx`.
|
|
346
|
+
- Sidebar `+` button placement: header row of the PROJECTS section keeps a small `+` (lucide `Plus`) for fast add (user decision: dual-entry). Same handler as picker overlay's `添加本地项目`.
|
|
347
|
+
- `EmptyState.tsx` resolves the H1 project name by reading the active picker selection. Falls back to the brand-neutral question when the active selection has no `displayName`.
|
|
348
|
+
- `Topbar.tsx` renders only the right-side stack icon (matches image3). When a session is open it can also display the session title left-aligned (image2). No 64px row, no dividing border.
|
|
349
|
+
- `useCollapsed("project:{projectKey}")` is the single hook for ProjectTree expansion. Default state: collapsed = false (expanded) for the active project on first load, collapsed = true for others. Persistence is best-effort, missing key = expanded.
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Risks
|
|
354
|
+
|
|
355
|
+
- IPC reshape: existing `sendMessage` contract change (Promise<PersistentEvent[]> → Promise<{ accepted: true }>) is a breaking renderer-side change. Acceptable because GUI and renderer ship together and are not pre-1.0.
|
|
356
|
+
- Adding lucide-react / react-markdown / shiki to GUI bundle increases renderer size; verified against build smoke.
|
|
357
|
+
- Shiki lazy load in Electron renderer must not assume CDN — same `createHighlighterCore` + bundle-shipped grammar approach as webui.
|
|
358
|
+
- `tool-blocks/registry` must not introduce a circular import. Keep registration module a leaf; `Transcript.tsx` imports `lookupToolBlock`, registry imports nothing renderer-specific beyond `Default*Block`.
|
|
359
|
+
- `webContents.send` from main with high-frequency events (`text_delta`) can saturate IPC. S0069 only forwards `PersistentEvent`; transient flooding is S0070's concern (RAF batcher solves it).
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Out Of Scope (Reaffirmed)
|
|
364
|
+
|
|
365
|
+
Codex screenshot elements explicitly excluded:
|
|
366
|
+
|
|
367
|
+
- empty-state plugin recommendation cards.
|
|
368
|
+
- bottom-sidebar global "对话" history group.
|
|
369
|
+
- composer review banner (`2 个文件已更改 +95 -1` / `审查`).
|
|
370
|
+
- "不使用项目" picker option.
|
|
371
|
+
- functional model picker / mic / "完全访问" toggle.
|