mulmoterminal 4.8.4 → 4.9.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/README.md +8 -0
- package/common/promptChannel.ts +22 -0
- package/common/promptHistory.ts +19 -0
- package/common/sharedAppPreview.ts +159 -0
- package/common/sharedAppViewVocabulary.ts +83 -0
- package/dist/assets/{abnfDiagram-N423BO3Z-D0pzqjdS-Ddm0Lrd5.js → abnfDiagram-N423BO3Z-D0pzqjdS-tsokipTX.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-Cqhmai3f-B3dDUaH7.js +1 -0
- package/dist/assets/{architectureDiagram-T3A2C74G-CX-Yoe5G-yzop4EEp.js → architectureDiagram-T3A2C74G-CX-Yoe5G-DgpViJOA.js} +1 -1
- package/dist/assets/{blockDiagram-VBNYF7ZC-B1eQZQ68-BhRsxP7C.js → blockDiagram-VBNYF7ZC-B1eQZQ68-l6UrEUWt.js} +1 -1
- package/dist/assets/{c4Diagram-5PPSVZJV-SBrN9MrU-vAAW2UGL.js → c4Diagram-5PPSVZJV-SBrN9MrU-Bak3wKoW.js} +1 -1
- package/dist/assets/channel-DKMa7Sn5-Br0ptK8f.js +1 -0
- package/dist/assets/{chunk-2GRJ4B5K-DdkAx9o--DFeFLDb2.js → chunk-2GRJ4B5K-DdkAx9o--Btdgu33f.js} +1 -1
- package/dist/assets/{chunk-3NCLNEKW-CBztX243-CjPSYONl.js → chunk-3NCLNEKW-CBztX243-DSaECkf1.js} +1 -1
- package/dist/assets/{chunk-4I5QYGJK-CmBJaHzs-BVfwtXeH.js → chunk-4I5QYGJK-CmBJaHzs-B0Cdj__3.js} +1 -1
- package/dist/assets/{chunk-5RXB4S5H-iaipMW_h-BcLk_INy.js → chunk-5RXB4S5H-iaipMW_h-DTYhZhRx.js} +1 -1
- package/dist/assets/{chunk-6Q2QTUOP-Cx8nz5_t-B32v5SJA.js → chunk-6Q2QTUOP-Cx8nz5_t-CzxkXkeW.js} +1 -1
- package/dist/assets/{chunk-7Z6QIM7H-CZs6l8Md-tr9mR4K7.js → chunk-7Z6QIM7H-CZs6l8Md-vvG9hcRg.js} +1 -1
- package/dist/assets/{chunk-GF5L2VYU-9OEKHEe4-CW-Meu0p.js → chunk-GF5L2VYU-9OEKHEe4-0OT7at6j.js} +1 -1
- package/dist/assets/{chunk-I66GZJ75-DSPCVCny-l2HSjV-d.js → chunk-I66GZJ75-DSPCVCny-CcYhEHb5.js} +1 -1
- package/dist/assets/{chunk-J7OUQ5F2-CQoryswr-CrX6L1Kb.js → chunk-J7OUQ5F2-CQoryswr-BPyac73K.js} +2 -2
- package/dist/assets/{chunk-JQJVKLGR-6F2ui09L-BKooN5JN.js → chunk-JQJVKLGR-6F2ui09L-B9WKQa27.js} +1 -1
- package/dist/assets/{chunk-KBJHAD2P-DXXCSVOS-D7kbeutk.js → chunk-KBJHAD2P-DXXCSVOS-DTQzS_lP.js} +1 -1
- package/dist/assets/{chunk-NSK5VX7P-DQgMj0Kn-Bw0EXLyr.js → chunk-NSK5VX7P-DQgMj0Kn-DHwNEvsc.js} +1 -1
- package/dist/assets/{chunk-QR6OTTB3-BQl8P7pD-BrYk4-mC.js → chunk-QR6OTTB3-BQl8P7pD-NaScgAMT.js} +1 -1
- package/dist/assets/{chunk-UBXNYLIW-BbdYEvTI-a0daIers.js → chunk-UBXNYLIW-BbdYEvTI-Cw1DJYdl.js} +1 -1
- package/dist/assets/{chunk-W5SLKNZC-DSJLk1SI-O5s2Awcm.js → chunk-W5SLKNZC-DSJLk1SI-2433ih9f.js} +1 -1
- package/dist/assets/{chunk-WRU74C26-DOE_TBL8-CIlYcho2.js → chunk-WRU74C26-DOE_TBL8-TwO1OeO3.js} +1 -1
- package/dist/assets/classDiagram-JCYQIIEL-C1ePmEG1-B62snhgn.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-C_O3VNBf-B62snhgn.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-DvSvuXnG-DwKiuHWt.js +1 -0
- package/dist/assets/{cynefinDiagram-MW4NZA55-DuBeESb6-BAgFtGyK.js → cynefinDiagram-MW4NZA55-DuBeESb6-DESaAtqc.js} +1 -1
- package/dist/assets/{dagre-VZM6K2ZE-W5cB3U70-BgmnJW7-.js → dagre-VZM6K2ZE-W5cB3U70-CMnlYH3n.js} +1 -1
- package/dist/assets/{diagram-7IWD3JNH-Fk1aWqfX-DGwD7DXy.js → diagram-7IWD3JNH-Fk1aWqfX-DRcZC0ZJ.js} +1 -1
- package/dist/assets/{diagram-B4RE2ZJO-q-8xygfU-DVivZcei.js → diagram-B4RE2ZJO-q-8xygfU-tDoHZfs1.js} +1 -1
- package/dist/assets/{diagram-LBJQPF4R-1SKCcOis-CD_clTT_.js → diagram-LBJQPF4R-1SKCcOis-D0wi_ISa.js} +1 -1
- package/dist/assets/{diagram-Q27KOJAE-CtW4StAQ-yVlkAXlP.js → diagram-Q27KOJAE-CtW4StAQ-Dog4fjWc.js} +1 -1
- package/dist/assets/{diagram-UB23O5K3-r5fK-tbF-Cm5H_JWn.js → diagram-UB23O5K3-r5fK-tbF-BcyhXO2Z.js} +1 -1
- package/dist/assets/{dist-BxKmq7Uq.js → dist-CztsQcLD.js} +1 -1
- package/dist/assets/{dist-BRn3ps3m.js → dist-DXiBDDNa.js} +1 -1
- package/dist/assets/{dist-mEjqELMM.js → dist-DZ_5WoC2.js} +1 -1
- package/dist/assets/{dist-CNr_oHm2.js → dist-DejxKqvO.js} +1 -1
- package/dist/assets/{ebnfDiagram-BXEA7PRR-BZoIqTyx-Bw99h3-z.js → ebnfDiagram-BXEA7PRR-BZoIqTyx-O2S-Ztbj.js} +1 -1
- package/dist/assets/{erDiagram-JOGREHBK-CM8pI2JI-CtzfSaMS.js → erDiagram-JOGREHBK-CM8pI2JI-ClxE9ArW.js} +1 -1
- package/dist/assets/eventmodeling-45OFAUF4-jdctDg0T-DoXp2eXw.js +1 -0
- package/dist/assets/flowDiagram-UKHOOZJN-CR3dcoRl-DgzdW-cs.js +1 -0
- package/dist/assets/{ganttDiagram-PKOTCBZU-DYTkxDIh-BjxX7Vhw.js → ganttDiagram-PKOTCBZU-DYTkxDIh-BWid4w0-.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-C4cKwl5j-BlK_wv9x.js +1 -0
- package/dist/assets/{gitGraphDiagram-DS77QQ5N-DafidN4D-yIelzCVW.js → gitGraphDiagram-DS77QQ5N-DafidN4D-B2rG9g1c.js} +1 -1
- package/dist/assets/index-CXypRX5w.js +731 -0
- package/dist/assets/index-VWLTHNzn.css +1 -0
- package/dist/assets/info-DKCQHKI2-DPUCBYWS-BzVbMGm2.js +1 -0
- package/dist/assets/{infoDiagram-6WML65LV-DxSxsg1o-DKOEHEot.js → infoDiagram-6WML65LV-DxSxsg1o-CBIrfawM.js} +1 -1
- package/dist/assets/{ishikawaDiagram-WSZJBQD7-kAIMMFB5-C4MXhxbS.js → ishikawaDiagram-WSZJBQD7-kAIMMFB5-BUr-KlRN.js} +1 -1
- package/dist/assets/{journeyDiagram-NVQOT4AX-ClybI8XI-Ds5HDHBL.js → journeyDiagram-NVQOT4AX-ClybI8XI-CLfVRFK4.js} +1 -1
- package/dist/assets/{kanban-definition-27J2QSJJ-DDYHyW1q-DkdZkkm8.js → kanban-definition-27J2QSJJ-DDYHyW1q-BAhFiewn.js} +1 -1
- package/dist/assets/{lib-D3rnYeEZ.js → lib-F7cE_AFU.js} +1 -1
- package/dist/assets/{line-CtRAsVKI-CvHPq2CJ.js → line-CtRAsVKI-9L65MT5n.js} +1 -1
- package/dist/assets/{marp-DRrHU1pF.js → marp-CIsjVw1j.js} +1 -1
- package/dist/assets/{mermaid-parser.core-C1baW7NG-DDDjJ3Iw.js → mermaid-parser.core-C1baW7NG-BzDlu5Ub.js} +2 -2
- package/dist/assets/{mermaid.core-CGR3z-ay-EBcXYmNO.js → mermaid.core-CGR3z-ay-CjhO0dwd.js} +3 -3
- package/dist/assets/{mindmap-definition-FAOFIHXS-DU-wbYKK-DT9Gpl2W.js → mindmap-definition-FAOFIHXS-DU-wbYKK-BsksvxN4.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-CC7His0F-zsacmAHQ.js +1 -0
- package/dist/assets/{pegDiagram-VL7TDLO6-CZuV1UUk-D13C68Nw.js → pegDiagram-VL7TDLO6-CZuV1UUk-DqhlfAbV.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-CCrcq2sL-DMKX4oTe.js +1 -0
- package/dist/assets/{pieDiagram-7S7Q4E2Y-B6cd1CWd-4c4pOLzI.js → pieDiagram-7S7Q4E2Y-B6cd1CWd-BCJp-Agh.js} +1 -1
- package/dist/assets/{quadrantDiagram-CIZ2JOQS-BvGHVjRA-GO77tNdf.js → quadrantDiagram-CIZ2JOQS-BvGHVjRA-D49aYGir.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-C5TUrpHf-C9eW7eNu.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-CGuuFEd8-DaWTqvuI.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-DuoCSFZY-C4fjzt9_.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-ig-DG9e_-BMyXLUtT.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6-BkBgpyvu-Bvq8TVK0.js +1 -0
- package/dist/assets/{railroadDiagram-AXF67PYL-DDtBla5s-CqlMz_aB.js → railroadDiagram-AXF67PYL-DDtBla5s-K2XHXDJr.js} +1 -1
- package/dist/assets/{requirementDiagram-LRYGKXZP-DEh2MJlW-BICfSRsM.js → requirementDiagram-LRYGKXZP-DEh2MJlW-BSbaLCze.js} +1 -1
- package/dist/assets/{sankeyDiagram-W5VNT64P-CHkaBx7U-B-Fj5v-i.js → sankeyDiagram-W5VNT64P-CHkaBx7U-BmapScg3.js} +1 -1
- package/dist/assets/{sequenceDiagram-SI44F4Z6-D8tHDhWP-BDgxby-Y.js → sequenceDiagram-SI44F4Z6-D8tHDhWP-CUdmKVJE.js} +1 -1
- package/dist/assets/{stateDiagram-OKZ733FA-KNjKsfsT-DHhMsUGV.js → stateDiagram-OKZ733FA-KNjKsfsT-C4vYhP8-.js} +1 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-B88w7Ous-BdVKMAxq.js +1 -0
- package/dist/assets/{swimlanes-SLNWSIFB-D_yJNxBK-BE0URE3x.js → swimlanes-SLNWSIFB-D_yJNxBK-DKm4q9Sk.js} +1 -1
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-DNcZzqTp-7j8MQOf5.js +8 -0
- package/dist/assets/{timeline-definition-Z64GVDOM-BcEcbSq1-DKUX3Xm0.js → timeline-definition-Z64GVDOM-BcEcbSq1-CgoBgtz4.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-CqfT0I1z-DFVDoISi.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-CiJkiGWo-CHV83aQM.js +1 -0
- package/dist/assets/{vennDiagram-T6HMQDX7-CxS3fcVg-CswRwiIx.js → vennDiagram-T6HMQDX7-CxS3fcVg-DsaLFuAp.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-D1tuVHAV-DAoS-Peb.js +1 -0
- package/dist/assets/{wardleyDiagram-T6FBY63Y-D_XCiR14-CphfvlPV.js → wardleyDiagram-T6FBY63Y-D_XCiR14-DrUj15Q7.js} +1 -1
- package/dist/assets/{xychartDiagram-ELKLHX3M-BU4yBDeE-D2Tm1WX2.js → xychartDiagram-ELKLHX3M-BU4yBDeE-NFXplYhk.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +18 -18
- package/server/backends/remoteHost/skillOverrides.spec.ts +95 -0
- package/server/backends/remoteHost/skillOverrides.ts +75 -0
- package/server/backends/remoteHost/skills.spec.ts +55 -0
- package/server/backends/remoteHost/skills.ts +11 -2
- package/server/backends/sharedApp/appViews.ts +44 -304
- package/server/backends/sharedApp/context.ts +18 -17
- package/server/backends/sharedApp/declare.ts +77 -42
- package/server/backends/sharedApp/establish.ts +132 -0
- package/server/backends/sharedApp/exclusivity.ts +10 -14
- package/server/backends/sharedApp/headlessHarness.ts +181 -0
- package/server/backends/sharedApp/headlessPreview.ts +680 -0
- package/server/backends/sharedApp/headlessReport.ts +169 -0
- package/server/backends/sharedApp/manifestWrite.ts +1 -1
- package/server/backends/sharedApp/modalCall.ts +13 -1
- package/server/backends/sharedApp/preview.ts +266 -0
- package/server/backends/sharedApp/previewWrite.ts +259 -0
- package/server/backends/sharedApp/publicForm.ts +32 -59
- package/server/backends/sharedApp/publicView.ts +63 -11
- package/server/backends/sharedApp/publish.ts +219 -202
- package/server/backends/sharedApp/records.ts +15 -12
- package/server/backends/sharedApp/scopedFields.ts +39 -75
- package/server/backends/sharedApp/serialize.ts +2 -2
- package/server/backends/sharedApp/slug.ts +30 -30
- package/server/backends/sharedApp/unpublish.ts +16 -18
- package/server/backends/sharedApp/viewDefects.ts +97 -0
- package/server/backends/sharedAppPreviewRoutes.ts +182 -0
- package/server/config/header-resolve.ts +0 -7
- package/server/index.ts +15 -0
- package/server/infra/jsonl-file.ts +50 -15
- package/server/infra/shared-app-tool.ts +48 -54
- package/server/infra/shell-quote.ts +26 -0
- package/server/routes/app-routes.ts +5 -0
- package/server/routes/hook-routes.ts +36 -5
- package/server/routes/session-routes.ts +13 -0
- package/server/routes/ws-routes.ts +14 -11
- package/server/session/activity-hook.ts +14 -2
- package/server/session/cleared-transcripts.ts +56 -6
- package/server/session/codex-activity-track.ts +20 -1
- package/server/session/lifecycle.ts +9 -1
- package/server/session/project-dir.ts +8 -0
- package/server/session/prompt-history-memo.ts +120 -0
- package/server/session/prompt-history.ts +204 -0
- package/server/session/pty-spawn.ts +19 -1
- package/server/session/registry.ts +13 -0
- package/server/session/session-reads.ts +200 -4
- package/server/session/shell-command.ts +59 -5
- package/server/session/spawn-codex.ts +1 -0
- package/server/session/spawn-deps.ts +4 -0
- package/server/session/spawn-shell.ts +12 -10
- package/server/skills/mulmoterminal-shared-app/SKILL.md +164 -42
- package/server/skills/mulmoterminal-shared-app/templates/gym.md +185 -3
- package/server/skills/mulmoterminal-shared-app/templates/meeting-room.md +91 -5
- package/server/skills/mulmoterminal-shared-app/templates/salon.md +71 -5
- package/dist/assets/architecture-TIHT7OUA-Cqhmai3f-HNSvDsRh.js +0 -1
- package/dist/assets/channel-DKMa7Sn5-4_LkXGcc.js +0 -1
- package/dist/assets/classDiagram-JCYQIIEL-C1ePmEG1-BkjWFYGd.js +0 -1
- package/dist/assets/classDiagram-v2-OCEON4UE-C_O3VNBf-BkjWFYGd.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-DvSvuXnG-DXolKKTT.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-jdctDg0T-IVR75jsC.js +0 -1
- package/dist/assets/flowDiagram-UKHOOZJN-CR3dcoRl-cm5H2rmK.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-C4cKwl5j-jgABZhxe.js +0 -1
- package/dist/assets/index-BjTAj1ZH.css +0 -1
- package/dist/assets/index-kOF5XoeM.js +0 -625
- package/dist/assets/info-DKCQHKI2-DPUCBYWS-CTvcbrmq.js +0 -1
- package/dist/assets/packet-7NZHBO7P-CC7His0F-DLiGf3VI.js +0 -1
- package/dist/assets/pie-RZYD4A2V-CCrcq2sL-CJY5vNco.js +0 -1
- package/dist/assets/radar-I7S5WNFK-C5TUrpHf-xojORAgJ.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-CGuuFEd8-xZrmS9-k.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-DuoCSFZY-BSiRvmcA.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-ig-DG9e_-q5gOwm3T.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6-BkBgpyvu-Bb-FNWut.js +0 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-B88w7Ous-D3xk7UbX.js +0 -1
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-DNcZzqTp-C52kpsnJ.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-CqfT0I1z-DzOQeDAW.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-CiJkiGWo-smwlEFxd.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-D1tuVHAV-C9hkZh3y.js +0 -1
- package/server/backends/sharedApp/deploy.ts +0 -349
- package/server/backends/sharedApp/staged.ts +0 -55
package/README.md
CHANGED
|
@@ -1365,6 +1365,14 @@ Favorited collections get their own toolbar buttons.
|
|
|
1365
1365
|
isn't available yet (Codex writes its rollout only once the turn ends).
|
|
1366
1366
|
- **Tools pane** — the available GUI tools plus a live tool-call history for the active
|
|
1367
1367
|
session.
|
|
1368
|
+
- **Prompts pane** — the prompts *you* sent the enlarged cell's session, newest first, from
|
|
1369
|
+
`GET /api/transcript/prompts`. The mirror of the Timeline above: that one is what the agent
|
|
1370
|
+
ran, this one is what you asked it for — for when several cells are running and you can no
|
|
1371
|
+
longer remember which one you told what. Read-only; click a long prompt to open it in place.
|
|
1372
|
+
Left open, it keeps up by itself. Claude and Codex; it reads what you TYPED (claude's own
|
|
1373
|
+
prompt history, codex's rollout), so a prompt sent mid-turn is there and text a skill
|
|
1374
|
+
injected is not. A `/clear` draws a line: the pane shows what you have asked *since*, the
|
|
1375
|
+
same way the header, the title and the last reply all stop describing the ended conversation.
|
|
1368
1376
|
- **Notifications** (🔔) — a toolbar bell with an unread badge and a dropdown of active
|
|
1369
1377
|
notifications; click a row to jump to its session.
|
|
1370
1378
|
- **Star MulmoTerminal** — a star button in the grid toolbar that stars the project on GitHub
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { isRecord } from "./isRecord.js";
|
|
2
|
+
|
|
3
|
+
// The pub/sub channel carrying "the user just submitted a prompt to this session". Both sides
|
|
4
|
+
// decide from it — the server publishes on the hook, the prompts pane listens to know its list
|
|
5
|
+
// grew — so the name and the payload shape live here rather than as a string literal on each side.
|
|
6
|
+
//
|
|
7
|
+
// A channel of its own rather than the `sessions` activity row, which is what a reader would reach
|
|
8
|
+
// for first and which cannot answer this: an activity publish is suppressed when the flag does not
|
|
9
|
+
// MOVE (nextActivity returns null, deliberately, so an unchanged row cannot flood the socket). A
|
|
10
|
+
// prompt sent while the agent is already working leaves `working` true either way, so it publishes
|
|
11
|
+
// nothing — and that prompt, the one interrupting a running turn, is exactly the one the pane
|
|
12
|
+
// exists to show (#1748, found by Codex on #1749).
|
|
13
|
+
export const PROMPT_SUBMITTED_CHANNEL = "prompt-submitted";
|
|
14
|
+
|
|
15
|
+
/** The session the prompt was typed at — our id, the one a cell holds. */
|
|
16
|
+
export interface PromptSubmittedEvent {
|
|
17
|
+
sessionId: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Both sides go through this rather than re-spelling the payload: the publisher pins it with
|
|
21
|
+
* `satisfies`, the subscriber narrows an untyped pub/sub frame with it. */
|
|
22
|
+
export const isPromptSubmittedEvent = (data: unknown): data is PromptSubmittedEvent => isRecord(data) && typeof data.sessionId === "string";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// The wire shape of GET /api/transcript/prompts — the prompts the user sent one session (#1748).
|
|
2
|
+
//
|
|
3
|
+
// In `common/` because both sides decide from it: the server builds the window (which log it reads
|
|
4
|
+
// and how it caps is its own business, in server/session/prompt-history.ts), and the pane renders
|
|
5
|
+
// each field. A shape kept twice is the one that drifts.
|
|
6
|
+
|
|
7
|
+
export interface PromptEntry {
|
|
8
|
+
/** Epoch ms, or null when the record carried no readable time — a prompt with an odd clock is
|
|
9
|
+
* still a prompt, so the TIME is what goes missing rather than the line. */
|
|
10
|
+
at: number | null;
|
|
11
|
+
text: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PromptWindow {
|
|
15
|
+
/** Oldest first; the pane reverses for display. */
|
|
16
|
+
prompts: PromptEntry[];
|
|
17
|
+
/** Older prompts exist that this does not carry. */
|
|
18
|
+
truncated: boolean;
|
|
19
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// What the preview route sends, decided by BOTH sides.
|
|
2
|
+
//
|
|
3
|
+
// Here rather than in `server/` with a copy in `src/` because that is what this repository's rule
|
|
4
|
+
// says about a wire shape the two ends agree on: a rename or a new field on one side would drift
|
|
5
|
+
// silently until the pane drew nothing, and "nothing drawn" is the failure mode this whole feature
|
|
6
|
+
// exists to remove. The server's own result type extends this with what only it uses.
|
|
7
|
+
//
|
|
8
|
+
// Design: `plans/feat-shared-app-preview.md`.
|
|
9
|
+
import type { Viewer } from "@receptron/sharedapp/view";
|
|
10
|
+
|
|
11
|
+
/** Who a page was written for. Three audiences, three DOCUMENTS with three sets of rules — never
|
|
12
|
+
* one page shown three ways. Reading them as interchangeable is how a page written for the front
|
|
13
|
+
* desk gets published to the world. */
|
|
14
|
+
export type PreviewAudience = "public" | "member" | "roster";
|
|
15
|
+
|
|
16
|
+
/** One page the author can look at. */
|
|
17
|
+
export interface PreviewPage {
|
|
18
|
+
id: string;
|
|
19
|
+
html: string;
|
|
20
|
+
audience: PreviewAudience;
|
|
21
|
+
/** WHO the author is to this page, and what they may change — the same
|
|
22
|
+
* `{ me, can }` mulmoserver posts to the live `/m/` and `/p/`, computed from
|
|
23
|
+
* the same projection by the same function.
|
|
24
|
+
*
|
|
25
|
+
* Absent on a public page, which has no reader and no roles: `public.submit`
|
|
26
|
+
* is judged from the declaration alone, and a `viewer` there would be an
|
|
27
|
+
* answer to a question that page never asks.
|
|
28
|
+
*
|
|
29
|
+
* It comes from the SERVER because the client has neither the projection nor
|
|
30
|
+
* the author's verified address. Sending the projection instead and letting
|
|
31
|
+
* the pane resolve it would be the second implementation this whole change
|
|
32
|
+
* exists to remove. */
|
|
33
|
+
viewer?: Viewer;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type PreviewDataset = Record<string, unknown>[];
|
|
37
|
+
|
|
38
|
+
/** The records ONE page is handed, per collection.
|
|
39
|
+
*
|
|
40
|
+
* Per page rather than one map for the app, and that is the point rather than tidiness: a member
|
|
41
|
+
* page may name a collection `public.read` does not open, so a single map is either missing those
|
|
42
|
+
* records on the member page or handing them to the public one. The second is worse — it would
|
|
43
|
+
* show the author a public page drawing private data, which is the preview being LOOSER than
|
|
44
|
+
* production, the one thing it must never be. */
|
|
45
|
+
export type PreviewDatasets = Record<string, Record<string, PreviewDataset>>;
|
|
46
|
+
|
|
47
|
+
/** The key a page's datasets sit under. Audience-qualified because a view id is unique within its
|
|
48
|
+
* tier and not across them. */
|
|
49
|
+
export const previewPageKey = (audience: PreviewAudience, id: string): string => `${audience}:${id}`;
|
|
50
|
+
|
|
51
|
+
/** One input of a GENERATED public form — an app that declares `public.submit` and publishes no
|
|
52
|
+
* page of its own.
|
|
53
|
+
*
|
|
54
|
+
* Already reduced to what a visitor may fill in: `createFields` minus the address (compared to
|
|
55
|
+
* their token) and minus the status (pinned to `initialStatus`), in the order the declaration
|
|
56
|
+
* lists them. The reduction happens on the SERVER, through the same `writableFields` the published
|
|
57
|
+
* site's submit path uses, so the pane draws boxes rather than deciding which boxes exist. */
|
|
58
|
+
export interface PreviewFormField {
|
|
59
|
+
name: string;
|
|
60
|
+
label: string;
|
|
61
|
+
required: boolean;
|
|
62
|
+
/** The schema's type, so a `enum` draws a select and a `text` draws a textarea. A pane that drew
|
|
63
|
+
* every field as a text box would let the author submit values the real form could not produce. */
|
|
64
|
+
type: string;
|
|
65
|
+
/** An `enum`'s choices, which travel with it for the same reason they travel to the public page:
|
|
66
|
+
* a visitor cannot read the schema. */
|
|
67
|
+
values?: string[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The generated form, per collection. Empty for an app that publishes a page of its own. */
|
|
71
|
+
export type PreviewForm = Record<string, PreviewFormField[]>;
|
|
72
|
+
|
|
73
|
+
export interface SharedAppPreview {
|
|
74
|
+
aid: string;
|
|
75
|
+
/** What a public create may carry, per collection, exactly as the published config declares it.
|
|
76
|
+
*
|
|
77
|
+
* Carried to the browser because the PARENT judges a submission against it — an unknown cid, a
|
|
78
|
+
* value that is not a string, a field outside `createFields` — and those three judgements are
|
|
79
|
+
* most of what a preview is for. Handing the parent an empty map instead does not disable the
|
|
80
|
+
* check; it makes the check REFUSE EVERYTHING, and it refuses with `unknown-collection`, which
|
|
81
|
+
* reads as "your declaration is wrong" about a declaration that is fine. That shipped once
|
|
82
|
+
* (2026-08-14) and cost an author a debugging session pointed at the wrong repository. */
|
|
83
|
+
submit: Record<string, { createFields: string[] }>;
|
|
84
|
+
/** Every page this publish would put live, public first. */
|
|
85
|
+
pages: PreviewPage[];
|
|
86
|
+
/** Whether this publish would leave the app open to anonymous visitors. False is normal: an app
|
|
87
|
+
* with no `public` block is one only its roster ever sees. */
|
|
88
|
+
publicOpen: boolean;
|
|
89
|
+
/** Whether there was a live `apps/{aid}` to project from. Said out loud because the projection
|
|
90
|
+
* DEPENDS on it — keys publish does not own are carried forward from the live document, so a
|
|
91
|
+
* preview computed without one is the projection of a FIRST publish, not of the next one. */
|
|
92
|
+
fromLiveApp: boolean;
|
|
93
|
+
/** The app publishes a GENERATED form rather than a page of its own.
|
|
94
|
+
*
|
|
95
|
+
* Carried as a fact rather than left to be inferred from an empty `pages`: those two states put
|
|
96
|
+
* the same empty frame on screen and mean opposite things — "there is nothing to draw" against
|
|
97
|
+
* "there is something to draw and this pane cannot draw it yet". */
|
|
98
|
+
generatedForm: boolean;
|
|
99
|
+
/** The inputs of that generated form. Carried rather than left to the pane to derive: the pane
|
|
100
|
+
* cannot read the schema either, and a form it built from field NAMES alone would ask for
|
|
101
|
+
* different things than the published site does — a preview of nothing real. */
|
|
102
|
+
formInputs: PreviewForm;
|
|
103
|
+
datasets: PreviewDatasets;
|
|
104
|
+
/** Collections a page names but whose records could not be read. Reported rather than silently
|
|
105
|
+
* empty: "no bookings yet" and "the read was refused" put identical pixels on the screen. */
|
|
106
|
+
unreadable: string[];
|
|
107
|
+
/** Pages that will go out but are worth looking at first — see `viewWarnings`. */
|
|
108
|
+
warnings: string[];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The route's answer. Three shapes, and only one of them is a failure:
|
|
112
|
+
*
|
|
113
|
+
* - not declared — most directories are not shared apps, and the pane asks about whichever one a
|
|
114
|
+
* cell is open in. An error would make normal operation look like a fault.
|
|
115
|
+
* - declared and refused — the author's work in progress, and the reason the pane exists. These
|
|
116
|
+
* lines are the answer to the question asked; a status code could not carry them.
|
|
117
|
+
* - declared and computed. */
|
|
118
|
+
/** What the author accepted in the confirmation, on its way to the server. */
|
|
119
|
+
export interface PreviewSubmission {
|
|
120
|
+
cid: string;
|
|
121
|
+
/** Strings only. The rules compare stored values without coercing, so a number here would write a
|
|
122
|
+
* record that differs BY TYPE from the identical-looking one the published page writes. */
|
|
123
|
+
values: Record<string, string>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** One record a preview wrote, and the mirror that travelled with it.
|
|
127
|
+
*
|
|
128
|
+
* Both ends hold this: the server returns it, the pane remembers it, and the pane hands it back to
|
|
129
|
+
* have it removed. It is the ONLY place a preview's writes are known — the rules read a public
|
|
130
|
+
* create with `hasOnly(createFields)`, so nothing can mark the document itself. */
|
|
131
|
+
export interface PreviewWrittenRecord {
|
|
132
|
+
cid: string;
|
|
133
|
+
id: string;
|
|
134
|
+
mirror?: { cid: string; id: string } | undefined;
|
|
135
|
+
/** The ONLY thing undo accepts, and the reason it is here rather than the record above.
|
|
136
|
+
*
|
|
137
|
+
* Undo runs through the author's own Firestore handle, so a route that took a cid and an id
|
|
138
|
+
* would take ANY cid and id in the app: a caller reaching this server could delete a stranger's
|
|
139
|
+
* real booking and put the slot it was holding back to `open`, and every write would be
|
|
140
|
+
* perfectly authorized. The cid and the id are still carried, because the author has to be able
|
|
141
|
+
* to read what this preview wrote — but they are for the SCREEN. What the server acts on is a
|
|
142
|
+
* token it minted itself when it made the write, held in memory and mapped to that exact record.
|
|
143
|
+
*
|
|
144
|
+
* In memory, for the lifetime of the process, which is the same lifetime as the list on screen:
|
|
145
|
+
* neither survives a restart, and a preview's writes become ordinary records when it ends. */
|
|
146
|
+
token: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** A write whose outcome is unknown: the request threw after the server may already have written.
|
|
150
|
+
*
|
|
151
|
+
* Kept because the alternative is worse. Dropping it leaves a real record in the app that nothing
|
|
152
|
+
* on this screen can name, so the author cannot remove what they cannot see. */
|
|
153
|
+
export interface PreviewUncertainWrite {
|
|
154
|
+
cid: string;
|
|
155
|
+
uncertain: true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type SharedAppPreviewResponse =
|
|
159
|
+
{ declared: false } | { declared: true; ok: false; problems: string[] } | { declared: true; ok: true; preview: SharedAppPreview };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// What the parent's own words MEAN, in one place, for everything that explains a view to its author.
|
|
2
|
+
//
|
|
3
|
+
// Two things say these sentences now and they must not say them differently: the headless run an
|
|
4
|
+
// agent starts (`server/backends/sharedApp/headlessReport.ts`) and the log an author copies out of
|
|
5
|
+
// the Collections pane (`src/utils/sharedAppPreviewLog.ts`). Both are read by the same reader —
|
|
6
|
+
// often a language model, sometimes the person — about the same page, and the two arriving in
|
|
7
|
+
// different vocabularies would make an author reconcile two accounts of one failure before they
|
|
8
|
+
// could start on the failure.
|
|
9
|
+
//
|
|
10
|
+
// In `common/` because that is what the rule says (CLAUDE.md): a value BOTH sides decide from lives
|
|
11
|
+
// here rather than mirrored with a comment asking somebody to keep two copies in step.
|
|
12
|
+
//
|
|
13
|
+
// These are the explanations. What each host does with them — a per-page report, a timeline — is
|
|
14
|
+
// its own, and stays there.
|
|
15
|
+
import type { ViewNoticeCode } from "@receptron/sharedapp/view";
|
|
16
|
+
import type { PreviewAudience } from "./sharedAppPreview.js";
|
|
17
|
+
|
|
18
|
+
/** What the parent's refusals mean.
|
|
19
|
+
*
|
|
20
|
+
* None of these ever reaches a screen: they are answered on the port, into a promise the page
|
|
21
|
+
* usually does not await. So this is the only place an author can learn that the page asked for
|
|
22
|
+
* something the declaration does not allow — and the page, meanwhile, showed whatever it shows
|
|
23
|
+
* when nothing comes back. */
|
|
24
|
+
export const REFUSALS: Record<string, string> = {
|
|
25
|
+
"unknown-collection": "the page submitted to a collection this app's `public.submit` does not declare",
|
|
26
|
+
"undeclared-field": "the page sent a field that is not in that collection's `createFields`",
|
|
27
|
+
"not-a-submission":
|
|
28
|
+
"the message was not a submission at all — most often a value that is not a string (the rules compare stored values without coercing, so only strings may be sent)",
|
|
29
|
+
busy: "a confirmation was already open (the page submitted twice)",
|
|
30
|
+
cancelled: "the confirmation was declined",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** What a member or participant page is told when it asks for a write.
|
|
34
|
+
*
|
|
35
|
+
* `transition`, `assign` and `withdraw` reach the member parent, which judges them and would
|
|
36
|
+
* perform them on the live page — here it has nowhere to write, so it answers `read-only`. That
|
|
37
|
+
* is a GOOD outcome and has to read like one: the ask arrived, in the right shape, and was
|
|
38
|
+
* declined for the reason every preview declines a write.
|
|
39
|
+
*
|
|
40
|
+
* It replaced a longer note saying the parent answering intents "is not the one running here",
|
|
41
|
+
* which was true while the preview had only the public bridge and is not any more. */
|
|
42
|
+
export const READ_ONLY_ON_A_MEMBER_PAGE =
|
|
43
|
+
"the preview does not write, so it declined. The ask itself was well formed and reached the parent — this line means the control is wired, not that anything is wrong. " +
|
|
44
|
+
"Whether the deployed rules would accept the write is a different question and is not answered here.";
|
|
45
|
+
|
|
46
|
+
/** One refusal, in the words that fit the page it happened on. */
|
|
47
|
+
export const explainRefusal = (reason: string, audience: PreviewAudience): string => {
|
|
48
|
+
if (reason === "read-only" && audience !== "public") return READ_ONLY_ON_A_MEMBER_PAGE;
|
|
49
|
+
return REFUSALS[reason] ?? reason;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** What a page that never answered the handshake is doing, and the one thing that causes it. */
|
|
53
|
+
export const READY_DEADLOCK =
|
|
54
|
+
"It NEVER answered the handshake, so the parent sent it no records at all — this is the page that sits on its loading state forever. " +
|
|
55
|
+
"`ready()` has to be called OUTSIDE the `onState` callback: inside it, it can never run, because the parent sends no state until `ready` arrives.";
|
|
56
|
+
|
|
57
|
+
/** Why a `<form>` is a dead control here, in the words of what the browser actually does. */
|
|
58
|
+
export const FORM_BLOCKED =
|
|
59
|
+
"A form cannot submit here: the frame is sandboxed without `allow-forms`, and the browser blocks the submission BEFORE firing the `submit` event — so an " +
|
|
60
|
+
'`onsubmit` handler with `e.preventDefault()` as its first line never runs. Use a `<div>` with a `type="button"` button and a click handler.';
|
|
61
|
+
|
|
62
|
+
/** What the frame reported about ITSELF, and what to do about each.
|
|
63
|
+
*
|
|
64
|
+
* The codes come from `@receptron/sharedapp/view`, which normalises anything it does not know to
|
|
65
|
+
* `unknown` — so this map is over a closed set and an entry that goes missing is a type error
|
|
66
|
+
* rather than a code printed raw. */
|
|
67
|
+
export const NOTICES: Record<ViewNoticeCode, string> = {
|
|
68
|
+
error: "the page raised an error nothing caught. Everything after the throw did not run, which is usually why the screen stopped where it did",
|
|
69
|
+
"unhandled-rejection": "a promise the page made was rejected and nothing handled it. Most often an `await` on a request that was refused",
|
|
70
|
+
"modal-ignored":
|
|
71
|
+
"the page called a modal the sandbox IGNORES. `alert`, `confirm` and `prompt` do nothing here and do nothing on the published page either — `confirm` answers " +
|
|
72
|
+
"false, so the page carries on as though the visitor had said no. Ask with an `<input>`, say things in the page, and confirm by pressing twice",
|
|
73
|
+
"notices-dropped":
|
|
74
|
+
"the page reported more of the above than this runtime carries, and the rest were not sent. The ones above are the earliest, which are usually the cause",
|
|
75
|
+
unknown: "the page reported something under a name this host does not know. The name itself is not repeated here — it is a string the page chose",
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** Page-controlled text, put into a report so it cannot be mistaken for the report's own words.
|
|
79
|
+
*
|
|
80
|
+
* `JSON.stringify` rather than a pair of quotes: a label reading `Save "draft"` or a screen with a
|
|
81
|
+
* newline in it would otherwise end a quotation early, and the reader — frequently a model being
|
|
82
|
+
* asked what went wrong — cannot tell a quotation mark the page wrote from one a host did. */
|
|
83
|
+
export const quoteForReport = (text: string): string => JSON.stringify(text);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-CvsWqFln-Tc7ZSrRz.js";import{_ as n,g as r}from"./mermaid-parser.core-C1baW7NG-
|
|
1
|
+
import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-CvsWqFln-Tc7ZSrRz.js";import{_ as n,g as r}from"./mermaid-parser.core-C1baW7NG-BzDlu5Ub.js";import"./chunk-I66GZJ75-DSPCVCny-CcYhEHb5.js";import"./chunk-3NCLNEKW-CBztX243-DSaECkf1.js";import{n as i,r as a,t as o}from"./chunk-6Q2QTUOP-Cx8nz5_t-CzxkXkeW.js";import{t as s}from"./chunk-JWPE2WC7-BQ3zXr2k-DSd3Ct95.js";var c=r().RailroadAbnf.parser.LangiumParser,l=e(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=e(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=e(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=e(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=e(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=e(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=e(e=>{s(e,a),e.title&&a.setTitle(e.title),e.rules.map(e=>a.addRule(m(e)))},`populateDb`),g={parser:{parse:e(e=>{a.clear(),t.debug(`[ABNF Parser] Starting Langium parse`);let r=c.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new n(r);let i=r.value;t.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),t.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:a}},db:a,renderer:o,styles:i};export{g as diagram};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{f as e}from"./mermaid-parser.core-C1baW7NG-BzDlu5Ub.js";export{e as createArchitectureServices};
|