wiki-viewer 2.16.0 → 2.16.1

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.
Files changed (124) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +4 -4
  5. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/standalone/.next/server/app/api/agent/activity/route.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js.nft.json +1 -1
  16. package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/api/agent/fs/move/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/api/agent/fs/search/route.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/api/agent/settings/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/assets/[...path]/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/pdf/save/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/share/[token]/asset/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/share/[token]/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/share/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/system/browse/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/system/reveal/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/branch/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/refresh/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/system/workspaces/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/upload/[...path]/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/wiki/app/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/wiki/backlinks/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/wiki/content/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/wiki/download/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/wiki/folder/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/wiki/git-branches/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/wiki/git-checkout/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/wiki/git-diff/route.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/api/wiki/git-file-info/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/wiki/git-history/route.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/api/wiki/git-pull/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/wiki/move/route.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/api/wiki/new-file/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/api/wiki/outlinks/route.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/api/wiki/page/route.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/api/wiki/presence/route.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/api/wiki/route.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/api/wiki/scratch/route.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/api/wiki/search/route.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/api/wiki/slugs/route.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/api/wiki/upload/route.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/api/wiki/watch/route.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/page/react-loadable-manifest.json +4 -5
  60. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/app/s/[token]/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/signin/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ltsov-._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_workspaces_[id]_open_route_actions_1087xu7.js +1 -1
  65. package/.next/standalone/.next/server/chunks/ssr/0.u4_next_0kf.7hj._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/ssr/0.u4_next_dist_0xugq-k._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_chunks_mermaid_core_chunk-5ZQYHXKU_mjs_0z1.978._.js +1 -1
  68. package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_chunks_mermaid_core_chunk-KSCS5N6A_mjs_0v4oeem._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/ssr/_01eqklo._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/ssr/_0858xdh._.js +1 -1
  71. package/.next/standalone/.next/server/chunks/ssr/_0k6g8yo._.js +3 -3
  72. package/.next/standalone/.next/server/chunks/ssr/_0sr4wj.._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_06613~i._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0o~d81.._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0szp2v0._.js +1 -1
  76. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  77. package/.next/standalone/.next/server/pages/500.html +1 -1
  78. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  79. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  80. package/.next/standalone/.next/static/chunks/0.5ywu7_j899v.js +12 -0
  81. package/.next/standalone/.next/static/chunks/{0tjcc1wg-57l1.js → 065s1_n.1k11t.js} +1 -1
  82. package/.next/standalone/.next/static/chunks/{0y~5s_skz1l9a.js → 0ccw-uc_3ao.w.js} +1 -1
  83. package/.next/standalone/.next/static/chunks/{0ii.akeq7k4pf.js → 0f8kfd9d6kq0b.js} +1 -1
  84. package/.next/standalone/.next/static/chunks/0lsw6lsaj7lby.js +1 -0
  85. package/.next/standalone/.next/static/chunks/0rpxacw15~o7k.js +12 -0
  86. package/.next/standalone/.next/static/chunks/{0r7d-1tq61fa1.js → 0s2h7kvkrtq~p.js} +1 -1
  87. package/.next/standalone/.next/static/chunks/{0enm-3xy21jcx.js → 0tzi1~wpigp2y.js} +2 -2
  88. package/.next/standalone/.next/static/chunks/117dkzyg4c~us.js +1 -0
  89. package/.next/standalone/.next/static/chunks/{0j7kp7w2oum68.js → 11pa_9ucyrzh9.js} +1 -1
  90. package/.next/standalone/.scratch/tweak-running-app/craft-checklist.md +52 -0
  91. package/.next/standalone/.scratch/tweak-running-app/issues/01-isolated-origin.md +108 -0
  92. package/.next/standalone/.scratch/tweak-running-app/issues/01-runtime-containment.md +18 -0
  93. package/.next/standalone/.scratch/tweak-running-app/issues/02-injection-transform.md +18 -0
  94. package/.next/standalone/.scratch/tweak-running-app/issues/02-tracer-bullet.md +48 -0
  95. package/.next/standalone/.scratch/tweak-running-app/issues/03-accept-carbonize-mutator.md +19 -0
  96. package/.next/standalone/.scratch/tweak-running-app/issues/03-run-robustness.md +37 -0
  97. package/.next/standalone/.scratch/tweak-running-app/issues/04-multifile-safety.md +37 -0
  98. package/.next/standalone/.scratch/tweak-running-app/issues/04-static-tracer-bullet.md +27 -0
  99. package/.next/standalone/.scratch/tweak-running-app/issues/05-batch-parity.md +43 -0
  100. package/.next/standalone/.scratch/tweak-running-app/issues/05-dynamic-node-app.md +22 -0
  101. package/.next/standalone/.scratch/tweak-running-app/issues/06-remote-transport-bridge.md +20 -0
  102. package/.next/standalone/.scratch/tweak-running-app/issues/07-retire-bespoke-web-tweak.md +17 -0
  103. package/.next/standalone/.scratch/tweak-running-app/keep-ticket.md +74 -0
  104. package/.next/standalone/.scratch/tweak-running-app/mockups.html +305 -0
  105. package/.next/standalone/.scratch/tweak-running-app/ops1-updated.md +215 -0
  106. package/.next/standalone/.scratch/tweak-running-app/ops12-continuation.md +44 -0
  107. package/.next/standalone/.scratch/tweak-running-app/ops22-context.md +84 -0
  108. package/.next/standalone/.scratch/tweak-running-app/pivot-note.md +25 -0
  109. package/.next/standalone/.scratch/tweak-running-app/qa-live-engine.ts +104 -0
  110. package/.next/standalone/.scratch/tweak-running-app/spike.md +256 -0
  111. package/.next/standalone/.scratch/tweak-running-app/suggest-current-assessment.md +39 -0
  112. package/.next/standalone/.scratch/tweak-running-app/suggest-inline-interaction-spec.md +320 -0
  113. package/.next/standalone/.scratch/tweak-running-app/tweak-interaction-spec.md +161 -0
  114. package/.next/standalone/.scratch/tweak-running-app/two-actions-interaction-spec.md +234 -0
  115. package/.next/standalone/.scratch/tweak-running-app/unified-live-spec.md +261 -0
  116. package/.next/standalone/package.json +1 -1
  117. package/.next/standalone/server.js +1 -1
  118. package/package.json +1 -1
  119. package/.next/standalone/.next/static/chunks/04jvwlttwa_sw.js +0 -12
  120. package/.next/standalone/.next/static/chunks/161kgz.8h.3iq.js +0 -1
  121. package/.next/standalone/.next/static/chunks/16kzqur8~yg6b.js +0 -12
  122. /package/.next/standalone/.next/static/{7EbGXTo8ObPhr7UobKARQ → zwUX5EOT_44n1RoB_W9Bo}/_buildManifest.js +0 -0
  123. /package/.next/standalone/.next/static/{7EbGXTo8ObPhr7UobKARQ → zwUX5EOT_44n1RoB_W9Bo}/_clientMiddlewareManifest.js +0 -0
  124. /package/.next/standalone/.next/static/{7EbGXTo8ObPhr7UobKARQ → zwUX5EOT_44n1RoB_W9Bo}/_ssgManifest.js +0 -0
@@ -0,0 +1,305 @@
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>Comments + Suggestions — better UX mockups</title>
7
+ <style>
8
+ :root{
9
+ --background:#ECEAE3; --foreground:#201F1B; --card:#F4F2EB; --popover:#F4F2EB;
10
+ --primary:#55552F; --primary-foreground:#F4F2EB; --secondary:#E2DFD6;
11
+ --muted:#E2DFD6; --muted-foreground:#75726A; --accent:#DEDBD1; --accent-soft:#D9D4AE;
12
+ --success:#16a34a; --success-soft:#dcfce7; --destructive:#dc2626; --destructive-soft:#fee2e2;
13
+ --warning:#d97706; --warning-soft:#fef3c7; --warning-ink:#a16207;
14
+ --border:#D8D5CB; --input:#B8B4A6; --link:#5C5C33; --radius:8px;
15
+ --shadow-card:0 4px 16px rgba(0,0,0,.04); --shadow-pop:0 8px 24px rgba(0,0,0,.10);
16
+ --shadow-dialog:0 16px 48px rgba(0,0,0,.14);
17
+ }
18
+ *{box-sizing:border-box}
19
+ html,body{margin:0}
20
+ body{
21
+ background:var(--background); color:var(--foreground);
22
+ font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
23
+ font-size:15px; line-height:1.5; padding:32px 20px 96px;
24
+ }
25
+ .wrap{max-width:1080px;margin:0 auto}
26
+ h1.page{font-size:24px;margin:0 0 4px;font-weight:650}
27
+ .sub{color:var(--muted-foreground);margin:0 0 4px;max-width:80ch}
28
+ .legend{display:flex;gap:16px;flex-wrap:wrap;margin:16px 0 32px;font-size:12.5px;color:var(--muted-foreground)}
29
+ .legend b{color:var(--foreground);font-weight:600}
30
+ section{margin:0 0 40px}
31
+ .sec-h{display:flex;align-items:baseline;gap:10px;margin:0 0 4px}
32
+ .sec-h .n{font:600 12px ui-monospace,Menlo,monospace;background:var(--primary);color:var(--primary-foreground);border-radius:5px;padding:2px 7px}
33
+ .sec-h h2{font-size:17px;margin:0;font-weight:600}
34
+ .sec-note{color:var(--muted-foreground);font-size:13px;margin:2px 0 14px;max-width:74ch}
35
+ .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:20px;align-items:start}
36
+ .stage{
37
+ background:var(--card);border:1px solid var(--border);border-radius:12px;
38
+ padding:22px;position:relative;box-shadow:var(--shadow-card);overflow:hidden;
39
+ }
40
+ .stage.paper{background:#F7F5EE}
41
+ .cap{font-size:12px;color:var(--muted-foreground);margin-top:10px}
42
+ .cap b{color:var(--foreground);font-weight:600}
43
+
44
+ .doc{font-family:Georgia,"Times New Roman",serif;font-size:16px;line-height:1.7;color:#2a2823}
45
+ .doc h3{font-size:19px;margin:.2em 0 .5em;font-weight:600}
46
+ .doc p{margin:0 0 1em}
47
+
48
+ .btn{font:500 13px inherit;border-radius:7px;padding:7px 12px;border:1px solid var(--border);
49
+ background:var(--card);color:var(--foreground);cursor:default;display:inline-flex;align-items:center;gap:6px}
50
+ .btn.pri{background:var(--primary);color:var(--primary-foreground);border-color:var(--primary)}
51
+ .btn.ghost{background:transparent}
52
+ .btn.sm{padding:4px 9px;font-size:12px}
53
+ .btn.link{border:none;background:none;color:var(--muted-foreground);padding:4px 6px}
54
+ .kbd{font:600 11px ui-monospace,monospace;color:var(--muted-foreground)}
55
+
56
+ .bubble{display:inline-flex;align-items:center;gap:2px;background:var(--popover);
57
+ border:1px solid var(--border);border-radius:8px;padding:5px;box-shadow:var(--shadow-pop)}
58
+ .bubble .ic{width:30px;height:30px;display:grid;place-items:center;border-radius:6px;color:#3a3831;font-size:14px}
59
+ .bubble .ic.act{color:var(--primary);font-weight:600}
60
+ .bubble .sep{width:1px;height:20px;background:var(--border);margin:0 4px}
61
+ .bubble .lab{font-size:12px;padding:0 8px;height:30px;display:inline-flex;align-items:center;gap:6px;border-radius:6px;color:#3a3831}
62
+ .bubble .lab.hl{background:var(--accent-soft)}
63
+
64
+ .composer{background:var(--popover);border:1px solid var(--border);border-radius:10px;
65
+ padding:12px;box-shadow:var(--shadow-pop);max-width:400px}
66
+ .ta{width:100%;border:1px solid var(--input);border-radius:7px;background:#FBFAF5;
67
+ padding:9px 10px;font:15px inherit;color:var(--foreground);resize:none;min-height:60px}
68
+ .ta::placeholder{color:var(--muted-foreground)}
69
+ .crow{display:flex;align-items:center;justify-content:space-between;margin-top:10px;gap:8px}
70
+ .crow .left{display:flex;align-items:center;gap:6px}
71
+ .hint{font-size:11px;color:var(--muted-foreground)}
72
+
73
+ .icard{border:1px solid var(--border);border-radius:9px;background:var(--card);padding:11px 12px;max-width:400px}
74
+ .icard .top{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--muted-foreground);margin-bottom:5px}
75
+ .icard .body{font-family:Georgia,serif;font-size:14.5px;color:#2a2823}
76
+ .icard .meta{font-size:11.5px;color:var(--muted-foreground);margin-top:6px}
77
+ .icard .acts{display:flex;gap:6px;justify-content:flex-end;margin-top:9px}
78
+ .pill{font:600 11px inherit;border-radius:999px;padding:2px 8px;display:inline-flex;gap:5px;align-items:center}
79
+ .pill.open{background:var(--secondary);color:var(--muted-foreground)}
80
+ .pill.agent{background:var(--accent-soft);color:#4a4a26}
81
+ .pill.drift{background:var(--warning-soft);color:var(--warning-ink)}
82
+
83
+ .ins{color:var(--success);text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:2px}
84
+ .del{color:var(--destructive);text-decoration:line-through;text-decoration-thickness:1.5px}
85
+ .rep-old{color:var(--destructive);text-decoration:line-through}
86
+ .rep-new{color:var(--success);text-decoration:underline;text-underline-offset:2px}
87
+ .blk-ins{border-left:3px solid var(--success);background:var(--success-soft);padding:2px 10px;border-radius:0 6px 6px 0;display:block}
88
+ .blk-del{border-left:3px solid var(--destructive);background:var(--destructive-soft);padding:2px 10px;border-radius:0 6px 6px 0;display:block;opacity:.85}
89
+ .agent-range{background-image:repeating-linear-gradient(45deg,transparent,transparent 4px,rgba(85,85,47,.10) 4px,rgba(85,85,47,.10) 6px);border-radius:3px;padding:0 2px}
90
+ .caret-badge{display:inline-grid;place-items:center;width:16px;height:16px;border-radius:4px;background:var(--primary);
91
+ color:var(--primary-foreground);font-size:10px;vertical-align:middle;margin:0 2px;transform:translateY(-1px)}
92
+ .comment-pip{display:inline-grid;place-items:center;width:16px;height:16px;border-radius:4px;background:var(--accent-soft);
93
+ color:#4a4a26;font-size:10px;vertical-align:middle;margin:0 2px;transform:translateY(-1px)}
94
+
95
+ .pop{background:var(--popover);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-dialog);
96
+ padding:11px;max-width:340px}
97
+ .pop .ph{display:flex;align-items:center;justify-content:space-between;font-size:11.5px;color:var(--muted-foreground);margin-bottom:8px}
98
+ .switch{display:inline-flex;align-items:center;gap:8px;font:600 11.5px inherit;color:var(--foreground)}
99
+ .switch button{border:none;background:var(--secondary);border-radius:5px;width:20px;height:20px;color:var(--foreground)}
100
+ .diff{font-family:Georgia,serif;font-size:14px;background:#FBFAF5;border:1px solid var(--border);border-radius:7px;padding:8px 10px;line-height:1.6}
101
+ .pop .acts{display:flex;gap:7px;justify-content:flex-end;margin-top:10px}
102
+
103
+ .banner{display:flex;align-items:center;gap:8px;background:var(--accent-soft);color:#4a4a26;
104
+ border-radius:8px;padding:7px 11px;font-size:12.5px;max-width:400px}
105
+ .statusbar{display:flex;align-items:center;gap:14px;background:var(--card);border:1px solid var(--border);
106
+ border-radius:8px;padding:7px 12px;font-size:12px;color:var(--muted-foreground);max-width:460px}
107
+ .statusbar .chip{margin-left:auto;background:var(--secondary);border-radius:999px;padding:3px 10px;color:var(--foreground);font-weight:600;display:inline-flex;gap:6px;align-items:center}
108
+ .radio{display:inline-flex;gap:4px;background:var(--secondary);border-radius:7px;padding:3px}
109
+ .radio span{padding:4px 10px;border-radius:5px;font-size:12px;color:var(--muted-foreground)}
110
+ .radio span.on{background:var(--card);color:var(--foreground);box-shadow:0 1px 2px rgba(0,0,0,.06)}
111
+
112
+ /* copy-as-prompt panel */
113
+ .panel{background:var(--popover);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-pop);max-width:440px}
114
+ .panel .ph{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--border);font-weight:600;font-size:13px}
115
+ .plist{padding:6px}
116
+ .pitem{display:flex;gap:9px;align-items:flex-start;padding:8px 8px;border-radius:7px}
117
+ .pitem:hover{background:var(--secondary)}
118
+ .pitem .k{font:600 10.5px ui-monospace,monospace;border-radius:5px;padding:2px 6px;flex:none;margin-top:1px}
119
+ .k.comment{background:var(--accent-soft);color:#4a4a26}
120
+ .k.sugg{background:var(--success-soft);color:#166534}
121
+ .pitem .txt{font-size:12.5px;color:#2a2823;line-height:1.4}
122
+ .pitem .txt code{font-family:ui-monospace,monospace;font-size:11px;background:var(--secondary);border-radius:4px;padding:1px 4px}
123
+ .pitem .copy{margin-left:auto;flex:none}
124
+ .pfoot{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 12px;border-top:1px solid var(--border)}
125
+ .promptbox{width:100%;border:1px solid var(--input);border-radius:7px;background:#FBFAF5;padding:9px;
126
+ font:12px ui-monospace,monospace;color:#2a2823;white-space:pre-wrap;line-height:1.55}
127
+ .toast{background:#201F1B;color:#F4F2EB;border-radius:8px;padding:9px 12px;font-size:12.5px;display:flex;gap:9px;align-items:center;max-width:380px;box-shadow:var(--shadow-dialog)}
128
+ .anno{position:absolute;top:10px;right:12px;font:600 10.5px ui-monospace,monospace;color:var(--muted-foreground);background:var(--secondary);border-radius:5px;padding:2px 7px}
129
+ </style>
130
+ </head>
131
+ <body>
132
+ <div class="wrap">
133
+ <h1 class="page">Comments + Suggestions — better UX</h1>
134
+ <p class="sub">No new modes. Exactly two actions, both already persisted: <b>Comment</b> (discuss/annotate) and <b>Suggest</b> (propose an edit to review). The work is (S) making Suggest a real inline tracked-change experience, and (T) a <b>Copy as prompt</b> surface that serializes the comments/suggestions you already have into a prompt you can paste into your own agent.</p>
135
+ <div class="legend">
136
+ <span><span class="ins">green underline</span> = insertion</span>
137
+ <span><span class="del">red strike</span> = deletion</span>
138
+ <span><span class="rep-old">old</span> <span class="rep-new">new</span> = replacement</span>
139
+ <span><span class="agent-range">✦ hatched</span> = agent-authored</span>
140
+ </div>
141
+
142
+ <!-- ============ COMMENT (existing, minor polish) ============ -->
143
+ <section>
144
+ <div class="sec-h"><span class="n">T · 1</span><h2>Comment — unchanged, already persisted</h2></div>
145
+ <p class="sec-note">Nothing new here. Select text → Comment → a persisted thread anchored to the block. This is where you "say what you want" in prose. Shown for completeness; it is the raw material Copy-as-prompt reads.</p>
146
+ <div class="grid">
147
+ <div class="stage" style="text-align:center;padding:30px">
148
+ <div class="bubble" style="margin:0 auto">
149
+ <span class="ic act">B</span><span class="ic" style="font-style:italic">I</span>
150
+ <span class="ic" style="text-decoration:underline">U</span><span class="ic">&lt;/&gt;</span>
151
+ <span class="sep"></span><span class="ic">🔗</span><span class="sep"></span>
152
+ <span class="lab hl">💬 Comment</span><span class="lab hl">✎ Suggest</span>
153
+ </div>
154
+ <div class="cap">The two actions. No third button, no mode toggle.</div>
155
+ </div>
156
+ <div class="stage">
157
+ <div class="icard">
158
+ <div class="top"><span>💬</span><span>you</span><span class="pill open">open</span></div>
159
+ <div class="body">"Tighten this paragraph, keep the citation."</div>
160
+ <div class="meta">on block <code style="font-family:ui-monospace,monospace;font-size:11px">“The rendering pipeline…”</code></div>
161
+ <div class="acts"><span class="btn ghost sm">Reply</span><span class="btn ghost sm">Resolve</span></div>
162
+ </div>
163
+ <div class="cap">A normal, persisted comment. Exactly today's feature.</div>
164
+ </div>
165
+ </div>
166
+ </section>
167
+
168
+ <!-- ============ COPY AS PROMPT (new surface over existing) ============ -->
169
+ <section>
170
+ <div class="sec-h"><span class="n">T · 2</span><h2>Copy as prompt — a surface over existing comments + suggestions</h2></div>
171
+ <p class="sec-note">This does not create anything. It gathers the <b>open comments and pending suggestions already on the document</b> and serializes them into a prompt string. Copy one item, or copy all. Paste into whatever agent you use. Same string format the old tweak flow produced.</p>
172
+ <div class="grid">
173
+ <div class="stage">
174
+ <span class="anno">panel</span>
175
+ <div class="panel">
176
+ <div class="ph"><span>Copy as prompt · notes.md</span><span class="btn ghost sm">Copy all</span></div>
177
+ <div class="plist">
178
+ <div class="pitem"><span class="k comment">comment</span><span class="txt"><code>The rendering pipeline…</code> — Tighten this paragraph, keep the citation.</span><span class="copy btn ghost sm">⎘</span></div>
179
+ <div class="pitem"><span class="k comment">comment</span><span class="txt"><code>Next steps</code> — Add a short TODO list here.</span><span class="copy btn ghost sm">⎘</span></div>
180
+ <div class="pitem"><span class="k sugg">suggestion</span><span class="txt"><code>paints the result…</code> — replace with "renders to the canvas"</span><span class="copy btn ghost sm">⎘</span></div>
181
+ </div>
182
+ <div class="pfoot"><span class="hint">3 open items · unresolved only</span><span class="btn ghost sm">Show text ▾</span></div>
183
+ </div>
184
+ <div class="cap"><b>Where it lives:</b> comments panel header + a per-item ⎘ on each comment/suggestion. Reads the persisted sidecar; touches nothing.</div>
185
+ </div>
186
+ <div class="stage">
187
+ <span class="anno">Show text</span>
188
+ <div class="composer" style="max-width:440px">
189
+ <div style="font-weight:600;font-size:13px;margin-bottom:6px">Prompt</div>
190
+ <div class="promptbox">Edit the file `notes.md` (a Markdown document). Apply these changes:
191
+
192
+ 1. `The rendering pipeline…`: Tighten this paragraph, keep the citation.
193
+ 2. `Next steps`: Add a short TODO list here.
194
+ 3. `paints the result…`: replace with "renders to the canvas"</div>
195
+ <div class="crow"><span class="hint">Clipboard needs https — copy manually if blocked.</span><span class="btn pri sm">Done</span></div>
196
+ </div>
197
+ <div class="cap">Manual-copy fallback for non-secure contexts. One code path, always numbered.</div>
198
+ </div>
199
+ </div>
200
+ </section>
201
+
202
+ <!-- ============ TRACK S ============ -->
203
+ <section>
204
+ <div class="sec-h"><span class="n">S · 1</span><h2>Suggest — inline redline in the prose (the real upgrade)</h2></div>
205
+ <p class="sec-note">Today suggestions render as cards floating beside the block, which is why they feel like comments. Instead, render them <b>inside</b> the document as tracked changes (ProseMirror decorations — never written to the <code>.md</code>). Word-level where small; block treatment when the change spans most of a block.</p>
206
+ <div class="stage paper">
207
+ <div class="doc">
208
+ <h3>Rendering pipeline</h3>
209
+ <p>The pipeline parses the source into an AST, <span class="del">then eventually </span>transforms it, and <span class="rep-old">paints the result to screen</span> <span class="rep-new">renders to the canvas</span>. Each stage is <span class="ins">independently </span>testable.</p>
210
+ <p><span class="blk-del">This paragraph is proposed for deletion in full — it repeats the intro and adds nothing.</span></p>
211
+ <p><span class="blk-ins">New paragraph proposed: caching happens between transform and paint, keyed by content hash, so unchanged blocks skip re-rendering.</span></p>
212
+ </div>
213
+ <div class="cap"><b>Word-level:</b> small insert/delete/replace shown in place. <b>Block-level:</b> left bar + soft wash for whole-block changes.</div>
214
+ </div>
215
+ </section>
216
+
217
+ <section>
218
+ <div class="sec-h"><span class="n">S · 2</span><h2>Review affordance + overlap switcher</h2></div>
219
+ <p class="sec-note">A change-anchored popover (opened from a small <span class="caret-badge">▾</span> badge, 44px touch slop) with the word-level diff and in-place Accept/Reject. Overlapping suggestions on one range cycle through a <b>1 of N</b> switcher instead of piling on the same pixel.</p>
220
+ <div class="grid">
221
+ <div class="stage paper">
222
+ <span class="anno">single</span>
223
+ <div class="doc" style="margin-bottom:14px">…and <span class="rep-old">paints the result to screen</span><span class="rep-new">renders to the canvas</span><span class="caret-badge">▾</span>.</div>
224
+ <div class="pop">
225
+ <div class="ph"><span>✎ suggestion · you</span></div>
226
+ <div class="diff"><span class="rep-old">paints the result to screen</span> → <span class="rep-new">renders to the canvas</span></div>
227
+ <div class="acts"><span class="btn ghost sm">Reject</span><span class="btn pri sm">Accept</span></div>
228
+ </div>
229
+ <div class="cap">Accept writes the <code>.md</code> (existing engine, base-hash gated). Reject discards.</div>
230
+ </div>
231
+ <div class="stage paper">
232
+ <span class="anno">overlap</span>
233
+ <div class="doc" style="margin-bottom:14px">Each stage is <span class="ins">independently </span><span class="caret-badge">▾3</span>testable.</div>
234
+ <div class="pop">
235
+ <div class="ph"><span>✎ suggestion</span><span class="switch"><button>◂</button> 1 of 3 <button>▸</button></span></div>
236
+ <div class="diff">insert <span class="ins">independently</span></div>
237
+ <div class="ph" style="margin:8px 0 0">by <b style="color:var(--foreground)">ana</b> · human</div>
238
+ <div class="acts"><span class="btn ghost sm">Reject</span><span class="btn pri sm">Accept</span></div>
239
+ </div>
240
+ <div class="cap">Accept supersedes the conflicting others (existing engine behavior).</div>
241
+ </div>
242
+ </div>
243
+ </section>
244
+
245
+ <section>
246
+ <div class="sec-h"><span class="n">S · 3</span><h2>Author attribution (human vs agent)</h2></div>
247
+ <p class="sec-note">Suggestions can be written by you or by an agent (an agent that edits your file writes ordinary suggestions). Same grammar, distinct styling so you can tell them apart at a glance: agent ranges get a hatched texture + <b>✦</b>.</p>
248
+ <div class="stage paper">
249
+ <div class="doc" style="margin-bottom:14px">
250
+ <span class="agent-range rep-old">The rendering pipeline is a fairly involved multi-stage process that</span> <span class="agent-range rep-new">The pipeline</span> parses, transforms, and paints. <span class="agent-range ins">(Smith 2021)</span><span class="caret-badge">▾</span>
251
+ </div>
252
+ <div class="pop">
253
+ <div class="ph"><span class="pill agent">✦ agent · claude</span></div>
254
+ <div class="diff"><span class="rep-old">The rendering pipeline is a fairly involved multi-stage process that</span> → <span class="rep-new">The pipeline</span></div>
255
+ <div class="acts"><span class="btn ghost sm">Reject</span><span class="btn pri sm">Accept</span></div>
256
+ </div>
257
+ <div class="cap">No special "instruction" linkage or lifecycle — it is just a suggestion with an author.</div>
258
+ </div>
259
+ </section>
260
+
261
+ <section>
262
+ <div class="sec-h"><span class="n">S · 4</span><h2>Suggesting mode (silent-revert killed)</h2></div>
263
+ <p class="sec-note">Keep the Editing / Suggesting radio. In Suggesting mode your typed edits show as <b>live inline redline</b> and post as suggestions on block-exit — instead of today's capture-then-silently-revert. A failed post keeps your text as a plain edit + a toast, so nothing is lost.</p>
264
+ <div class="grid">
265
+ <div class="stage">
266
+ <div class="banner" style="margin-bottom:12px">✎ Suggesting mode · your edits become suggestions</div>
267
+ <div class="statusbar">
268
+ <span>notes.md</span>
269
+ <span class="radio"><span>Editing</span><span class="on">Suggesting</span></span>
270
+ <span class="chip">✎ 3 suggestions</span>
271
+ </div>
272
+ <div class="cap">The status-bar chip jumps between changes.</div>
273
+ </div>
274
+ <div class="stage">
275
+ <span class="anno">post failed</span>
276
+ <div class="toast">⚠ Couldn't save that suggestion — kept as an edit. <span style="color:#D9D4AE;text-decoration:underline">Retry</span></div>
277
+ <div class="cap">Data-loss path closed. If the sidecar write fails, the text stays in the doc rather than vanishing on blur.</div>
278
+ </div>
279
+ </div>
280
+ </section>
281
+
282
+ <section>
283
+ <div class="sec-h"><span class="n">S · 5</span><h2>View-mode Suggest &nbsp;·&nbsp; drift / stale anchor</h2></div>
284
+ <p class="sec-note">The missing read-only affordance (contract drift today): a Suggest button beside Comment's view-mode twin. And an honest state when a suggestion's target moved.</p>
285
+ <div class="grid">
286
+ <div class="stage" style="text-align:center;padding:30px">
287
+ <div class="bubble" style="margin:0 auto"><span class="lab hl">💬 Comment</span><span class="lab hl">✎ Suggest</span></div>
288
+ <div class="cap"><b>View mode.</b> Both actions available read-only; redline popover hides Accept/Reject for a non-owner.</div>
289
+ </div>
290
+ <div class="stage paper">
291
+ <span class="anno">drift</span>
292
+ <div class="doc" style="margin-bottom:12px">…parses, transforms, and paints.<span class="caret-badge" style="background:var(--warning);color:#3a2a00">⚠</span></div>
293
+ <div class="icard" style="max-width:360px">
294
+ <div class="top"><span class="pill drift">⚠ target changed</span></div>
295
+ <div class="meta" style="margin-top:0">The block this suggestion pointed at was edited. Re-anchor to review, or remove it.</div>
296
+ <div class="acts"><span class="btn ghost sm">Re-anchor</span><span class="btn ghost sm">Remove</span></div>
297
+ </div>
298
+ <div class="cap">Never silently dropped. Accept-time 409 stays in the popover: "Document changed — refresh review."</div>
299
+ </div>
300
+ </div>
301
+ </section>
302
+
303
+ </div>
304
+ </body>
305
+ </html>
@@ -0,0 +1,215 @@
1
+ # Spec: Tweak on a running app (proxied node-app runtime)
2
+
3
+ ## Problem Statement
4
+
5
+ A user running wiki-viewer can already visually **Tweak** two kinds of content:
6
+ select an element, queue an instruction, let an agent propose a rewrite, review
7
+ it, and Accept to persist the change. This works for markdown blocks and for the
8
+ static HTML preview.
9
+
10
+ It does **not** work for a **proxied live surface** — a surface that must run
11
+ same-origin to function, of which a running node app is the first case. When a
12
+ user launches an app through the node-app runner and views it through the app
13
+ proxy, there is no Tweak surface at all. The picker is intentionally not injected
14
+ into proxied apps. So the one place a user most wants to point-and-fix ("make
15
+ this button bigger", "this heading is wrong") is the one place Tweak is
16
+ unavailable. The user has to leave the app, find the source by hand, and edit it
17
+ blind.
18
+
19
+ **Scope of the gap (verified against code).** Self-contained HTML — including
20
+ client-scripted *dynamic* HTML — is *already* fully tweakable today. The static
21
+ surface re-fetches the raw source, injects the picker, and renders it as an
22
+ opaque-origin `srcDoc` (`allow-scripts`, no `allow-same-origin`); the page's own
23
+ scripts execute and the picker targets the live script-rendered DOM. The gap is
24
+ not "dynamic HTML" in general. It is exactly the surfaces that **cannot** use
25
+ that opaque-origin trick because they must stay same-origin to work (real
26
+ backend/fetch, a running server). Those are served through the proxy. So the
27
+ real foundation this spec builds is a **surface-agnostic proxied live-surface
28
+ Tweak path**, with a running node app as its first and driving consumer; a
29
+ future "serve a dynamic HTML file through the proxy" case rides the identical
30
+ mechanism with no new machinery.
31
+
32
+ ## Solution
33
+
34
+ Bring Tweak to the running app. The user opens their app in the proxied view,
35
+ enters Tweak mode, and points at real elements on the live page. They queue one
36
+ or more instructions with the same queue model they already know (Comment /
37
+ Suggest / Tweak; select-again-to-update; remove one or Cancel all). Dispatching
38
+ sends the batch to the attached agent.
39
+
40
+ The agent proposes changes and, when the user Accepts, the source is written by
41
+ the **agent**, not the server. On Accept, wiki-viewer sends an approval to the
42
+ agent; the agent writes the multi-file source change, rebuilds/reloads the app,
43
+ and returns a receipt. wiki-viewer shows the user success (the running app now
44
+ reflects the change) or a clear failure with the reason, and the queue survives
45
+ a failure so the user can retry.
46
+
47
+ The experience matches the existing Tweak surfaces (core parity): same
48
+ vocabulary, same queue, same presence indicator, same single-outstanding-request
49
+ discipline, same Cancel/Connect affordances.
50
+
51
+ ## User Stories
52
+
53
+ 1. As a user viewing my running app through the proxy, I want to enter Tweak mode, so that I can point at real elements on the live page.
54
+ 2. As a user, I want to click an element in the running app and see it highlighted as the Tweak target, so that I know exactly what I am about to change.
55
+ 3. As a user, I want to type an instruction for the selected element ("make this heading larger"), so that the agent knows what to change.
56
+ 4. As a user, I want selecting the same element again to update its queued instruction in place, so that the queued count does not inflate.
57
+ 5. As a user, I want to queue several element tweaks before dispatching, so that I can describe a whole set of changes as one batch.
58
+ 6. As a user, I want to remove a single queued tweak, so that I can drop one idea without losing the rest.
59
+ 7. As a user, I want a Cancel action that clears all queued selections without dispatching, so that I can back out cleanly.
60
+ 8. As a user, I want the dispatch button to carry a surface-appropriate label, so that the action reads naturally on the running-app surface (core parity with markdown "Rewrite" / HTML "Apply").
61
+ 9. As a user, I want a presence indicator that shows whether an agent is listening, so that I know whether dispatching will be picked up.
62
+ 10. As a user with queued tweaks but no attached agent, I want the dispatch button to become "Connect an agent" and open the agent panel, so that I can attach one without losing my queue.
63
+ 11. As a user, I want only one Tweak run outstanding at a time, so that I am not racing multiple agent runs against the same app.
64
+ 12. As a user who dispatches while a run is already outstanding, I want a clear rejection that keeps my queued work, so that nothing is lost.
65
+ 13. As a user, I want the agent to return one or more reviewable proposals for my batch, so that I can decide before anything is written.
66
+ 14. As a user, I want to review a proposal for each queued element, so that I can accept the good ones.
67
+ 15. As a user, I want to Accept a proposal, so that the change is written to my app's source.
68
+ 16. As a user, when I Accept, I want wiki-viewer to send approval to the agent and have the agent perform the write, so that the write uses the agent's real toolchain across multiple files.
69
+ 17. As a user, I want the app to rebuild/reload after an accepted write, so that I immediately see the change live.
70
+ 18. As a user, I want a receipt after Accept that says success or failure, so that I am never left guessing whether the write landed.
71
+ 19. As a user whose accepted write fails, I want a clear reason and my queue preserved, so that I can retry or adjust.
72
+ 20. As a user, I want to Discard a proposal, so that the app source is left byte-identical.
73
+ 21. As a user, I want to Cancel a run while it is waiting on the agent, so that I free the outstanding slot without writing anything.
74
+ 22. As a user, I want a single accepted tweak to be able to span multiple source files, so that changes that legitimately touch several files still work.
75
+ 23. As a user, I want the running-app Tweak to refuse a stale write when the underlying source changed since the proposal, so that I do not silently clobber a concurrent edit.
76
+ 24. As a user, I want Tweak on a proxied app to respect the same path-containment and workspace-isolation guarantees as every other write, so that a tweak cannot escape its workspace.
77
+ 25. As a user editing a markdown file that a human has open, I want proxied-app writes to still honor the file-vs-collab safety, so that a live human edit is never overwritten by a tweak.
78
+ 26. As an agent, I want to receive a proxied-app Tweak batch through the same poll/reply channel I already use, so that I do not need a new transport.
79
+ 27. As an agent, I want to attach reviewable proposals to each queued item, so that the human can review before any write.
80
+ 28. As an agent, I want to receive an explicit Accept approval carrying the selected proposal and per-file base hashes, so that I write exactly what was approved.
81
+ 29. As an agent, I want to return a structured receipt (success with written files, or failure with a reason), so that wiki-viewer can report the outcome.
82
+ 30. As an admin, I want proxied-app Tweak to obey the existing app-runner permission model, so that only permitted users can drive writes against a running app.
83
+
84
+ ## Implementation Decisions
85
+
86
+ **Accept authority — agent writes on accept, returns receipt.** On human Accept,
87
+ wiki-viewer does not write source itself. It transitions the proposal to an
88
+ "approval sent" state and hands the agent an approval message containing the
89
+ selected proposal identity and the per-file base hashes captured at proposal
90
+ time. The agent performs the multi-file write, triggers the app
91
+ rebuild/reload, and replies with a receipt. wiki-viewer records the receipt and
92
+ surfaces success or failure to the user. This differs from the current static
93
+ HTML resolve flow, where the server commits the agent-supplied candidate
94
+ verbatim.
95
+
96
+ **Runtime attachment — inject the picker into the proxied running app.** Tweak
97
+ targets are chosen on the real running page served through the app proxy, not on
98
+ a static snapshot. The existing picker/protocol (postMessage, data-only ops,
99
+ origin+shape validation) is injected into the proxied app HTML. The isolated
100
+ origin and the injection layer must be **surface-agnostic** — keyed on "a
101
+ proxied live surface," not hard-wired to node-app specifics — so any future
102
+ proxied surface reuses the same path unchanged. The picker injection is already
103
+ surface-agnostic (it takes an HTML string); the work here is the origin and the
104
+ proxy wiring around it. This requires
105
+ serving proxied node-apps from a **dedicated isolated origin**, because the
106
+ proxied app iframe must keep allow-same-origin to function, so page JS could
107
+ otherwise reach the wiki-viewer parent document and forge an Accept. The static
108
+ preview gets its opaque origin free via srcDoc; a running app cannot, so a
109
+ separate origin is the only way to restore the postMessage/Accept trust
110
+ boundary. Serving from that isolated origin is the frontier work all other
111
+ running-app Tweak work blocks on. No HMR/dev-server variant machinery is
112
+ introduced; live per-variant preview before Accept is out of scope for this
113
+ phase.
114
+
115
+ **Source patch scope — agent-owned multi-file.** A single accepted tweak may
116
+ write more than one source file. The current v1 "candidate must edit exactly one
117
+ file" cap is lifted for this flow. Because the agent owns the write, multi-file
118
+ is natural; wiki-viewer's role is to carry the approval and record the receipt.
119
+
120
+ **UX — core parity.** The running-app surface reuses the shared Tweak
121
+ vocabulary, queue model, presence indicator, single-outstanding-request rule,
122
+ and the Cancel / Connect-an-agent affordances already defined for the markdown
123
+ and HTML surfaces. The dispatch button label is surface-specific.
124
+
125
+ **Transport reuse.** Dispatch continues to flow through the existing human→agent
126
+ request channel and the agent poll/reply channel. The request kinds and the live
127
+ store gain the states needed for the agent-write receipt loop rather than a new
128
+ transport.
129
+
130
+ **Receipt state machine (from the decisions above).** The per-item proposal
131
+ lifecycle for this flow extends the existing preview lifecycle with an
132
+ agent-write tail:
133
+
134
+ ```
135
+ requested -> ready -> resolving -> approval-sent -> writing -> committed
136
+ \-> receipt-failed (queue preserved, retryable)
137
+ ready -> discarded (Discard: no write, source byte-identical)
138
+ waiting -> cancelled (Cancel run: outstanding slot freed, no write)
139
+ (base hash mismatch at approval time) -> invalidated (refused, no write)
140
+ ```
141
+
142
+ `committed` is reached only when the agent's receipt reports success. A failed
143
+ receipt lands `receipt-failed` and preserves the queue for retry. Accept is
144
+ refused if the per-file base hashes no longer match at approval time (stale
145
+ guard).
146
+
147
+ **Write-safety obligations for the agent-performed write.** The agent's
148
+ multi-file write must still land inside workspace path containment and must
149
+ honor the file-vs-collab authority: a markdown file a human currently has open
150
+ (collab state `active`) must not be raw-overwritten by an accepted tweak. These
151
+ are existing guarantees the receipt loop must not bypass.
152
+
153
+ ## Testing Decisions
154
+
155
+ Good tests here exercise external behavior at the highest existing seams and
156
+ avoid asserting implementation details. No real dev server, browser, or E2E
157
+ harness is introduced in this phase.
158
+
159
+ **Primary seam — in-process route-handler tests.** Follow the established
160
+ pattern (point `HOME` at a temp dir, reset the live stores, call the exported
161
+ route handlers directly with `Request` objects), as in the existing
162
+ route-level live tests. Cover: batch dispatch and the single-outstanding-request
163
+ 409; agent attaching reviewable proposals to each queued item; the Accept
164
+ approval transition; the agent receipt path for both success (`committed`) and
165
+ failure (`receipt-failed`, queue preserved); Discard leaving source
166
+ byte-identical; Cancel-while-waiting freeing the slot with no write; multi-file
167
+ accepted write (cap lifted); base-hash drift refusing the write; and
168
+ workspace isolation in both directions.
169
+
170
+ **Secondary seam — pure-function picker injection.** Follow the existing
171
+ picker/protocol pure-function tests: injection placement into proxied-app HTML,
172
+ message origin and shape validation, and the guarantee that a picker message can
173
+ never encode a write/accept command shape.
174
+
175
+ **Store-level — receipt state machine.** Unit-test the new proposal state
176
+ transitions (approval-sent → writing → committed / receipt-failed) at the store
177
+ level, mirroring the existing live-store state tests.
178
+
179
+ **Prior art.** The route-level live collab tests, the batch markdown tests, the
180
+ web-tweak collab tests, the web-tweak picker pure-function tests, and the app
181
+ runner lifecycle tests are the models to extend. New tests raise the test floor
182
+ deliberately.
183
+
184
+ ## Out of Scope
185
+
186
+ * HMR / dev-server variant machinery and live per-variant preview before Accept
187
+ (the full "runtime adapter" option was not chosen).
188
+ * Server-side verbatim commit of the agent's patch for the proxied-app flow (the
189
+ server-commit accept authority was not chosen; the agent performs the write).
190
+ * A real-runtime end-to-end test harness that boots a sample app and drives a
191
+ browser (may be added later as its own ticket).
192
+ * Changing the existing markdown or static-HTML Tweak surfaces beyond what core
193
+ parity requires.
194
+ * Any change to the app-runner permission model itself.
195
+
196
+ ## Further Notes
197
+
198
+ **Already delivered (Phase 1, committed).** Markdown Tweak run recovery is done:
199
+ Cancel frees the outstanding request and discards previews; a run-generation
200
+ guard drops superseded/late dispatches; generation failures preserve the queue;
201
+ resolve failures retry the same proposal; the no-agent Rewrite button becomes
202
+ "Connect an agent"; dispatch uses the freshest snapshot revision. That work
203
+ establishes the core-parity affordances this phase reuses on the running-app
204
+ surface.
205
+
206
+ **Security gate.** Injecting the picker into a proxied app is the one genuinely
207
+ new risk surface. The picker was deliberately excluded from proxied app HTML
208
+ because the app must stay same-origin, letting page JS reach the parent and
209
+ forge an Accept. The gate is therefore a dedicated isolated proxy origin (the
210
+ frontier ticket), not a sandbox/CSP tweak: dropping allow-same-origin breaks the
211
+ app, and keeping it leaves the parent reachable. Only a separate origin restores
212
+ the trust boundary.
213
+
214
+ **Docs obligations.** `docs/ux-contracts.md` and the isometric architecture map
215
+ must be updated in the same change that ships behavior, per repo convention.
@@ -0,0 +1,44 @@
1
+ # OPS-12 continuation brief — static-HTML live-tweak tracer bullet
2
+
3
+ Status as of this session. Read before resuming.
4
+
5
+ ## Done + committed (verified green)
6
+ - **OPS-9/10/11 prefactors** — committed `7894ead`, `88c5c5b`, `5940791`. denied-segments (`.impeccable` contained everywhere), `injectOverlay` (pure), `carbonizeLiveVariant` (pure). Full suite green, review findings F1–F4 fixed.
7
+ - **OPS-12 contained-write** — committed `7c2c2a5`. `src/lib/fs/contained-write.ts`: `containedWrite({rootDir,relPath,expectedBaseHash?,content,baseFiles?})→{ok:true,written}|{ok:false,code:"BASE_DRIFT"|"PATH_DENIED"}`, `hashContent()`. `commitCandidate` delegates to it. Tests green.
8
+ - **OPS-12 engine lifecycle** — committed `117f6c2`. `src/lib/live-engine/{paths,client,supervisor}.ts` + `POST/DELETE /api/wiki/live-web/session` + stub-engine test. Contracts:
9
+ - `startEngine({workspaceId,relPath,appRoot})→LiveEngineInfo{port,token,state,generation,...}`, `getEngine(key)`, `stopEngine(key)`, `__setSpawnerForTest`.
10
+ - `LiveEngineClient({port,token,fetch?,host?})`: `.health/.poll({timeoutMs,leaseMs,types,signal})/.reply(body)/.status/.stop`.
11
+ - session route: loopback + CSRF + auth + workspace-containment guarded; returns `{ok,port,token}`.
12
+
13
+ Full suite last run: **726/726**, floor 691.
14
+
15
+ ## Remaining (the interlocking backend spine + UI + docs)
16
+ Owned target files: `src/lib/proof/live/scaffold-store.ts`, `src/lib/proof/live/web-bridge.ts`, `src/app/api/wiki/live-web/{resolve,status}/route.ts`, `src/tests/proof/live-web-bridge.test.ts`, plus viewer wiring (`website-viewer.tsx` + a `use-live-web-session` hook) and `docs/ux-contracts.md` §1.4 + isometric map.
17
+
18
+ Two worker rounds (gpt-5.6-sol, ids `7f063da6`) failed to produce files — spent turns on recon, never implemented. Do NOT re-delegate this to the same worker profile; either implement inline or use a stronger model with the full design inline.
19
+
20
+ ## BLOCKING FINDING — variant representation mismatch (needs a decision)
21
+ The plan said "reuse `web.tweak.variants`, no schema change." Verified against source: that does not hold cleanly.
22
+
23
+ - `src/lib/web-tweak/preview-store.ts` stores a variant as `{variantId,label,domPreviewOps,candidateSourcePatch{files:[{path,content}]},baseFiles:[{path,sha256}]}`. Accept commits `candidateSourcePatch` **verbatim** iff base hashes match. No carbonize.
24
+ - The OPS-10/11 prefactors assume the **Impeccable scaffold** model: overlay `live.js` mounts variant `<div data-impeccable-variant>` scaffolds with scoped CSS + `--p-*` knobs; Accept = `carbonizeLiveVariant(scaffold,...)`.
25
+ - The agent's reply path today (`/api/agent/live/web-preview` → `attachVariants`) delivers the wiki-viewer format, **not** an Impeccable scaffold string. So the bridge cannot obtain a scaffold to reply `done --file` to the engine, nor feed `carbonize`, without a new scaffold-carrying reply path.
26
+
27
+ ### Options
28
+ - **B (spike's chosen topology — recommended): bridge translates; add a scaffold-carrying reply.** wiki-viewer server runs the engine `/poll` loop; on `generate/steer` it enqueues on its attached-agent channel; the attached agent replies with an **Impeccable scaffold string**; bridge relays scaffold to engine (`done --file`); Accept = `carbonize` + `containedWrite`. Uses OPS-10/11 as designed. COST: needs a scaffold reply payload/route beyond the original 5 owned files (extend `/api/agent/live/*` or add one) — a scoping expansion, not just the 5 files. Requires the attached agent to know the Impeccable variant format (Impeccable skill).
29
+ - **C: reuse existing web-tweak variant model fully; drop carbonize for web.** Variants = existing DOM-ops + verbatim candidate patches; Accept = verbatim `containedWrite`; engine used only for picker/overlay/transport. Simplest reuse; but abandons OPS-11 carbonize for web surfaces and diverges from the engine overlay's native scaffold expectation (overlay would need to render candidate DOM ops instead of Impeccable variant divs — may fight `live.js`).
30
+ - **A: engine-native generation.** The user's chat agent (running the Impeccable skill) drives the engine `/poll` directly; wiki-viewer hosts engine + overlay + accept-through-writer only. Truest to OPS-11; thinnest wiki-viewer generate role; but wiki-viewer's attached-agent channel isn't the generator, which is a different product UX than the ticket's "existing attached agent" wording implies.
31
+
32
+ Recommendation: **B**, matching the spike's "product hosts engine, existing agent generates" topology, accepting the scaffold-reply-channel scope expansion. Confirm before building the spine.
33
+
34
+ ## Spine design (assuming B)
35
+ - `scaffold-store.ts`: own WAL connection to `~/.wiki-viewer/live.db` (getDb there is private; open a second connection w/ busy_timeout). Table `live_web_scaffold {id,workspace_id,rel_path,original_source,disk_base_hash,scaffold,scaffold_hash,chosen_variant_id,param_values(json),state,created_at,resolved_at}` + idempotency map `live_web_event {engine_event_id PK,workspace_id,rel_path,live_request_id,reply_json,created_at}`. Persist so recovery survives restart.
36
+ - `web-bridge.ts`: one loop per `{workspaceId,relPath}`; long-poll engine; on generate/steer → `enqueueRequest(kind:"web.tweak.variants")` for `getOrCreateSession(workspaceId)`; idempotent on `engine_event_id` (replay re-sends stored reply); backpressure on `OUTSTANDING_REQUEST`; when the agent's scaffold reply lands, store scaffold+hash and reply `done` to engine; generation + AbortController cancels stale loop on teardown; NO engine mutators; NO disk write.
37
+ - `resolve/route.ts` POST `{action,path,chosenVariantId?,paramValues?}`: accept → `carbonizeLiveVariant(scaffold,scaffoldHash,...)` (409 on NO_MARKERS/NO_VARIANT/BASE_DRIFT) → `containedWrite(expectedBaseHash:diskBaseHash)` (409 on BASE_DRIFT) → mark accepted + stopEngine; discard → drop scaffold + stopEngine. auth+containment+CSRF+loopback. Invariant: no write before Accept; Accept writes iff BOTH hashes match.
38
+ - `status/route.ts` GET `?path=`: session+bridge+engine liveness; engine dead + scaffold present ⇒ `recoverable:true`.
39
+ - test `live-web-bridge.test.ts`: stub engine via `__setSpawnerForTest` or injected client; mirror `web-tweak-collab.test.ts`; assert the 10 items in the ticket AC.
40
+ - viewer: `website-viewer.tsx` static-`.html` tweak mode starts a session and points the sandboxed `srcDoc` iframe at the engine (S0 GREEN: opaque-origin can reach `127.0.0.1:<port>` via `?token=`). Put lifecycle in `src/hooks/use-live-web-session.ts`.
41
+ - docs: `docs/ux-contracts.md` §1.4 + isometric map, same commit.
42
+
43
+ ## S0 (resolved GREEN)
44
+ `live-server.mjs:709` reflects `Access-Control-Allow-Origin` for any request carrying a valid `?token=` regardless of origin (comment covers opaque/remote overlay origins incl. OPTIONS preflight; OPTIONS returns 204 with CORS headers). So a sandboxed `srcDoc` iframe (Origin: null) drives the engine directly. No reverse proxy for the localhost static tracer bullet; proxy transport stays deferred to OPS-6.