projectkeeper 0.0.0-stage → 1.0.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.
Files changed (276) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/LICENSE +21 -0
  3. package/README.md +283 -2
  4. package/README.zh-CN.md +285 -0
  5. package/THIRD_PARTY_NOTICES.md +53 -0
  6. package/bin/pk.mjs +6 -0
  7. package/bin/postinstall.mjs +5 -0
  8. package/bin/strip-types.mjs +28 -0
  9. package/package.json +68 -4
  10. package/patches/README.md +111 -0
  11. package/patches/pi-ai-0.85.0.ts +122 -0
  12. package/patches/pi-ai-0.87.1.ts +122 -0
  13. package/scripts/apply-pi-streaming-patch.ts +86 -0
  14. package/skills/clerk/coverage/SKILL.md +35 -0
  15. package/skills/clerk/cross-check/SKILL.md +118 -0
  16. package/skills/clerk/deepen-dispatch/SKILL.md +69 -0
  17. package/skills/clerk/lane-follow-up/SKILL.md +94 -0
  18. package/skills/clerk/lane-plan/SKILL.md +107 -0
  19. package/skills/clerk/lane-slot/SKILL.md +113 -0
  20. package/skills/clerk/lane-topic/SKILL.md +116 -0
  21. package/skills/clerk/orientation/SKILL.md +160 -0
  22. package/skills/clerk/reconcile/SKILL.md +77 -0
  23. package/skills/clerk/skeleton-dispatch/SKILL.md +43 -0
  24. package/skills/clerk/spot-check/SKILL.md +52 -0
  25. package/skills/clerk/synthesis/SKILL.md +65 -0
  26. package/skills/table-follow-up/SKILL.md +42 -0
  27. package/src/cli.ts +319 -0
  28. package/src/codemap/README.md +13 -0
  29. package/src/codemap/engines.ts +102 -0
  30. package/src/codemap/facts.ts +504 -0
  31. package/src/context/agent-entry.ts +232 -0
  32. package/src/context/ask.ts +97 -0
  33. package/src/context/assemble.ts +1058 -0
  34. package/src/context/code-source.ts +103 -0
  35. package/src/context/k-briefs.ts +114 -0
  36. package/src/context/lineage.ts +100 -0
  37. package/src/context/object-brief.ts +300 -0
  38. package/src/context/owner-words.ts +191 -0
  39. package/src/context/pack-fixture.ts +342 -0
  40. package/src/context/project-rules.ts +160 -0
  41. package/src/intake/intake.ts +392 -0
  42. package/src/intake/material-rules.ts +166 -0
  43. package/src/intake/removal.ts +316 -0
  44. package/src/intake/skipped.ts +72 -0
  45. package/src/keeper/adjustment.ts +565 -0
  46. package/src/keeper/bounds/boundary.ts +509 -0
  47. package/src/keeper/bounds/command.ts +1665 -0
  48. package/src/keeper/bounds/paths.ts +164 -0
  49. package/src/keeper/bounds/reads.ts +718 -0
  50. package/src/keeper/bounds/shell-write-guard.ts +521 -0
  51. package/src/keeper/capabilities.ts +136 -0
  52. package/src/keeper/clerk-steps.ts +151 -0
  53. package/src/keeper/clerk-tools.ts +1424 -0
  54. package/src/keeper/conversation.ts +253 -0
  55. package/src/keeper/evidence.ts +662 -0
  56. package/src/keeper/fake-provider.ts +132 -0
  57. package/src/keeper/held.ts +15 -0
  58. package/src/keeper/history-tools.ts +163 -0
  59. package/src/keeper/jobs/answering.ts +53 -0
  60. package/src/keeper/key-check.ts +105 -0
  61. package/src/keeper/ledger-tools.ts +295 -0
  62. package/src/keeper/link-check.ts +185 -0
  63. package/src/keeper/merge.ts +260 -0
  64. package/src/keeper/note-origin.ts +56 -0
  65. package/src/keeper/numbers-check.ts +102 -0
  66. package/src/keeper/open-in-pi.ts +57 -0
  67. package/src/keeper/organize/absence.ts +266 -0
  68. package/src/keeper/organize/carried-on.ts +214 -0
  69. package/src/keeper/organize/clerk-coverage.ts +293 -0
  70. package/src/keeper/organize/clerk-prompts.ts +132 -0
  71. package/src/keeper/organize/clerk.ts +1395 -0
  72. package/src/keeper/organize/coverage-gate.ts +19 -0
  73. package/src/keeper/organize/coverage-tools.ts +765 -0
  74. package/src/keeper/organize/entries.ts +153 -0
  75. package/src/keeper/organize/entry-gate.ts +289 -0
  76. package/src/keeper/organize/fill-tools.ts +718 -0
  77. package/src/keeper/organize/follow-up.ts +280 -0
  78. package/src/keeper/organize/generation-check.ts +504 -0
  79. package/src/keeper/organize/graph.ts +230 -0
  80. package/src/keeper/organize/lane-tools.ts +289 -0
  81. package/src/keeper/organize/looked-tools.ts +54 -0
  82. package/src/keeper/organize/materials.ts +243 -0
  83. package/src/keeper/organize/owner-lines.ts +493 -0
  84. package/src/keeper/organize/place-tools.ts +216 -0
  85. package/src/keeper/organize/placement-inference.ts +1045 -0
  86. package/src/keeper/organize/placement-time.ts +320 -0
  87. package/src/keeper/organize/placing.ts +821 -0
  88. package/src/keeper/organize/positioning.ts +33 -0
  89. package/src/keeper/organize/readiness.ts +47 -0
  90. package/src/keeper/organize/reading.ts +401 -0
  91. package/src/keeper/organize/round-blocks.ts +65 -0
  92. package/src/keeper/organize/round-news.ts +164 -0
  93. package/src/keeper/organize/round-open.ts +586 -0
  94. package/src/keeper/organize/schedule-state.ts +39 -0
  95. package/src/keeper/organize/schedule.ts +137 -0
  96. package/src/keeper/organize/service.ts +409 -0
  97. package/src/keeper/organize/skills.ts +116 -0
  98. package/src/keeper/organize/slots.ts +85 -0
  99. package/src/keeper/organize/stage-gate.ts +175 -0
  100. package/src/keeper/organize/stage-tools.ts +471 -0
  101. package/src/keeper/organize/standing-notes.ts +141 -0
  102. package/src/keeper/organize/table-follow-up.ts +330 -0
  103. package/src/keeper/organize/takeover-clerk.ts +832 -0
  104. package/src/keeper/organize/takeover-state.ts +103 -0
  105. package/src/keeper/organize/takeover.ts +216 -0
  106. package/src/keeper/organize/update-pending.ts +535 -0
  107. package/src/keeper/organize/workbench-placement.ts +92 -0
  108. package/src/keeper/own-keys.ts +114 -0
  109. package/src/keeper/owner-text.ts +165 -0
  110. package/src/keeper/project-folder.ts +334 -0
  111. package/src/keeper/prompts.ts +101 -0
  112. package/src/keeper/references.ts +177 -0
  113. package/src/keeper/relook/compose.ts +120 -0
  114. package/src/keeper/relook/prompts.ts +36 -0
  115. package/src/keeper/roles.ts +259 -0
  116. package/src/keeper/round-tools.ts +227 -0
  117. package/src/keeper/route.ts +148 -0
  118. package/src/keeper/rules.ts +97 -0
  119. package/src/keeper/runtime.ts +1858 -0
  120. package/src/keeper/scope-tools.ts +194 -0
  121. package/src/keeper/sendback-text.ts +83 -0
  122. package/src/keeper/step-timing.ts +207 -0
  123. package/src/keeper/testing/clerk-script.ts +212 -0
  124. package/src/keeper/testing/streaming-arguments.ts +57 -0
  125. package/src/keeper/tools.ts +2340 -0
  126. package/src/keeper/utterance-tools.ts +64 -0
  127. package/src/keeper/verbatim.ts +397 -0
  128. package/src/ledger/adapters.ts +186 -0
  129. package/src/ledger/arrangements.ts +257 -0
  130. package/src/ledger/blame.ts +58 -0
  131. package/src/ledger/code-engine-child.ts +69 -0
  132. package/src/ledger/code-engine.ts +548 -0
  133. package/src/ledger/code.ts +607 -0
  134. package/src/ledger/docs.ts +294 -0
  135. package/src/ledger/git-read.ts +321 -0
  136. package/src/ledger/index.ts +1850 -0
  137. package/src/ledger/item-range.ts +16 -0
  138. package/src/ledger/lines.ts +328 -0
  139. package/src/ledger/numbering.ts +269 -0
  140. package/src/ledger/ranges.ts +74 -0
  141. package/src/ledger/rebuild-worker.ts +15 -0
  142. package/src/ledger/rebuild.ts +387 -0
  143. package/src/ledger/repo-scan.ts +181 -0
  144. package/src/ledger/schema.ts +450 -0
  145. package/src/ledger/sessions.ts +134 -0
  146. package/src/ledger/text-scan.ts +466 -0
  147. package/src/ledger/time.ts +185 -0
  148. package/src/ledger/ts-symbols.ts +180 -0
  149. package/src/ledger/views.ts +377 -0
  150. package/src/mcp/server.ts +127 -0
  151. package/src/mcp/tools.ts +187 -0
  152. package/src/model/ids.ts +23 -0
  153. package/src/model/k-types.ts +912 -0
  154. package/src/model/time.ts +85 -0
  155. package/src/model/types.ts +1382 -0
  156. package/src/model/views-k.ts +707 -0
  157. package/src/model/vocab.ts +269 -0
  158. package/src/process/analysis.ts +79 -0
  159. package/src/process/breakpoint-candidates.ts +383 -0
  160. package/src/process/breakpoints.ts +746 -0
  161. package/src/process/claim-check.ts +295 -0
  162. package/src/process/delivery-links.ts +52 -0
  163. package/src/process/facts.ts +487 -0
  164. package/src/process/generations.ts +138 -0
  165. package/src/process/index.ts +243 -0
  166. package/src/process/integration.ts +128 -0
  167. package/src/process/placement.ts +107 -0
  168. package/src/process/plan.ts +276 -0
  169. package/src/process/sendbacks.ts +244 -0
  170. package/src/process/text.ts +185 -0
  171. package/src/process/units.ts +543 -0
  172. package/src/process/unnumbered.ts +431 -0
  173. package/src/process/work.ts +884 -0
  174. package/src/scope/candidates.ts +86 -0
  175. package/src/scope/decisions.ts +229 -0
  176. package/src/scope/discover.ts +741 -0
  177. package/src/scope/ignore.ts +126 -0
  178. package/src/scope/ignored-place.ts +93 -0
  179. package/src/scope/skip.ts +161 -0
  180. package/src/scope/toolchain.ts +180 -0
  181. package/src/scope/worktree.ts +187 -0
  182. package/src/server/api.ts +549 -0
  183. package/src/server/app.ts +595 -0
  184. package/src/server/drafts-api.ts +170 -0
  185. package/src/server/folders-api.ts +243 -0
  186. package/src/server/graph-view.ts +639 -0
  187. package/src/server/http.ts +224 -0
  188. package/src/server/k-agent-api.ts +56 -0
  189. package/src/server/k-rounds-d99.ts +91 -0
  190. package/src/server/k-views.ts +405 -0
  191. package/src/server/keeper-page.ts +446 -0
  192. package/src/server/keys-api.ts +239 -0
  193. package/src/server/project-folder-api.ts +104 -0
  194. package/src/server/vendor.ts +14 -0
  195. package/src/server/workbench-content.ts +561 -0
  196. package/src/sources/anchor.ts +129 -0
  197. package/src/sources/files.ts +267 -0
  198. package/src/sources/gitobs.ts +41 -0
  199. package/src/sources/history.ts +231 -0
  200. package/src/sources/scan-worker.ts +10 -0
  201. package/src/sources/sessions/locate.ts +292 -0
  202. package/src/sources/sessions/read.ts +346 -0
  203. package/src/sources/sessions/scope.ts +42 -0
  204. package/src/sources/sessions/utterances.ts +223 -0
  205. package/src/sources/watch.ts +215 -0
  206. package/src/store/json-file.ts +50 -0
  207. package/src/store/paths.ts +39 -0
  208. package/src/store/project-store.ts +295 -0
  209. package/src/store/versions.ts +172 -0
  210. package/src/store/workspace.ts +216 -0
  211. package/src/table/check.ts +333 -0
  212. package/src/table/follow-up-tools.ts +30 -0
  213. package/src/table/follow-up.ts +344 -0
  214. package/src/table/import.ts +610 -0
  215. package/src/table/project-home.ts +61 -0
  216. package/src/table/sessions.ts +168 -0
  217. package/src/table/takeover.ts +483 -0
  218. package/src/util/git-check.ts +29 -0
  219. package/src/util/git.ts +411 -0
  220. package/src/util/own-ts.ts +15 -0
  221. package/src/util/paths.ts +225 -0
  222. package/template/FILLING-IN.md +148 -0
  223. package/template/examples/papertrail.json +747 -0
  224. package/template/projectkeeper.schema.json +1001 -0
  225. package/ui/app.js +1135 -0
  226. package/ui/failures.js +82 -0
  227. package/ui/flyout.js +120 -0
  228. package/ui/folder-chooser.js +264 -0
  229. package/ui/glossary.js +156 -0
  230. package/ui/graph-fit.js +157 -0
  231. package/ui/graph-material.js +61 -0
  232. package/ui/graph-palette.js +109 -0
  233. package/ui/graph-tools.js +62 -0
  234. package/ui/graph.js +1712 -0
  235. package/ui/index.html +31 -0
  236. package/ui/k/code-view.js +230 -0
  237. package/ui/k/draft-view.js +93 -0
  238. package/ui/k/k-views.css +224 -0
  239. package/ui/k/round-tree.js +450 -0
  240. package/ui/k/scope-k.js +159 -0
  241. package/ui/k-data.js +118 -0
  242. package/ui/k-fold.js +106 -0
  243. package/ui/k-process.css +302 -0
  244. package/ui/k-process.js +934 -0
  245. package/ui/keeper-activity.js +175 -0
  246. package/ui/keeper-view.js +801 -0
  247. package/ui/keeper.css +101 -0
  248. package/ui/key-order.js +62 -0
  249. package/ui/list-fold.js +67 -0
  250. package/ui/markdown.d.ts +21 -0
  251. package/ui/markdown.js +472 -0
  252. package/ui/placement.d.ts +92 -0
  253. package/ui/placement.js +445 -0
  254. package/ui/popover-place.d.ts +16 -0
  255. package/ui/popover-place.js +89 -0
  256. package/ui/popover.js +198 -0
  257. package/ui/project-folder.js +37 -0
  258. package/ui/shell-state.js +40 -0
  259. package/ui/strip-rows.js +171 -0
  260. package/ui/styles.css +492 -0
  261. package/ui/theme-pick.js +36 -0
  262. package/ui/theme.js +74 -0
  263. package/ui/themes/LICENSE.md +13 -0
  264. package/ui/themes/README.md +44 -0
  265. package/ui/themes/TOKENS.md +291 -0
  266. package/ui/themes/a1/board.css +1557 -0
  267. package/ui/themes/a1/pk.css +392 -0
  268. package/ui/themes/b1/board.css +1163 -0
  269. package/ui/themes/b1/pk.css +218 -0
  270. package/ui/themes/d2/board.css +518 -0
  271. package/ui/themes/d2/pk.css +243 -0
  272. package/ui/themes/guard.css +73 -0
  273. package/ui/themes/i1/board.css +776 -0
  274. package/ui/themes/i1/pk.css +223 -0
  275. package/ui/themes/manifest.json +13 -0
  276. package/ui/views.js +2796 -0
package/ui/k-data.js ADDED
@@ -0,0 +1,118 @@
1
+ // The increment K data layer (Spec v3.0 §6.3, §6.4, §6.17, §6.9, §6.7; src/model/views-k.ts lists the endpoints and
2
+ // the shapes). The UI only draws these views; it never invents content. An endpoint that does not exist yet answers
3
+ // 404 — the caller gets null and shows `No data for this yet`.
4
+ import { api } from './app.js';
5
+
6
+ const NO_DATA = 'No data for this yet';
7
+
8
+ // ── reads ─────────────────────────────────────────────────────────────────
9
+ // A 404 is an answer ("this end is not built yet"), not an error: null, cached, and the surface says so.
10
+ const missing = new Set();
11
+ async function read(path) {
12
+ if (missing.has(path)) return null;
13
+ try {
14
+ return await api(path);
15
+ } catch (e) {
16
+ if (/\b404\b/.test(e.message)) { missing.add(path); return null; }
17
+ throw e;
18
+ }
19
+ }
20
+
21
+ const pid = (id) => encodeURIComponent(id);
22
+ export const noData = () => NO_DATA;
23
+
24
+ /** ProcessView for the project, or null when the endpoint is not built yet. */
25
+ export async function getProcess(projectId) {
26
+ return read(`/api/projects/${pid(projectId)}/process`);
27
+ }
28
+ /** LineageView of one object: its dated path to now (§2.11). */
29
+ export async function getLineage(projectId, objectId) {
30
+ return read(`/api/projects/${pid(projectId)}/objects/${pid(objectId)}/lineage`);
31
+ }
32
+ /** VersionsView of one document-like object. */
33
+ export async function getVersions(projectId, objectId) {
34
+ return read(`/api/projects/${pid(projectId)}/objects/${pid(objectId)}/versions`);
35
+ }
36
+ /** CodeView (§6.17); its send-backs ride on the view (`CodeView.sendBacks`), so Code never waits on the process view
37
+ * for them. */
38
+ export async function getCode(projectId) {
39
+ return read(`/api/projects/${pid(projectId)}/code`);
40
+ }
41
+ /** CodeFileTextView: a file of the current version, found by the ledger's fileRefs (§6.17; a page from `from`). Throws
42
+ * with the server's reason (e.g. not in the current version) — the reader says it, rather than `No data`. */
43
+ export async function getCodeFile(projectId, { repo = null, path, from = 1 }) {
44
+ const q = new URLSearchParams({ path, ...(repo ? { repo } : {}), ...(from > 1 ? { from: String(from) } : {}) });
45
+ return api(`/api/projects/${pid(projectId)}/code/file?${q}`);
46
+ }
47
+
48
+ // ── §3.11 session drafts (server/drafts-api.ts) ─────────────────────────────
49
+ /** SessionDraftRef[] of the sessions an object stands on (a work item's session sources), each with its draft or none. */
50
+ export async function getObjectDrafts(projectId, objectId) {
51
+ const r = await read(`/api/projects/${pid(projectId)}/drafts?objectId=${pid(objectId)}`);
52
+ return r ? r.drafts : null;
53
+ }
54
+ /** SessionDraftView by draft id, session source id, `host:session` or session id. */
55
+ export async function getDraft(projectId, key) {
56
+ return read(`/api/projects/${pid(projectId)}/drafts/${pid(key)}`);
57
+ }
58
+
59
+ // ── §1.17 semantic patches (CKC-26 AC-4) ────────────────────────────────────
60
+ /** PatchView[], oldest first by when it happened; `Change log` shows the confirmed ones. Null when not built. */
61
+ export async function getPatches(projectId) {
62
+ const r = await read(`/api/projects/${pid(projectId)}/patches`);
63
+ return r ? r.patches : null;
64
+ }
65
+ /** One patch with its details text (the words `pk get SP-n` prints), by id or number. */
66
+ export async function getPatch(projectId, key) {
67
+ return read(`/api/projects/${pid(projectId)}/patches/${pid(key)}`);
68
+ }
69
+
70
+ // ── §2.12 an earlier generation's plan document as it stood (D82) ───────────
71
+ /** GenerationPlanTextView of one of a generation's plan documents (its index in the band's `planDocs`); a page from `from`. */
72
+ export async function getGenerationPlan(projectId, generationId, index, from = 1) {
73
+ return read(`/api/projects/${pid(projectId)}/generations/${pid(generationId)}/plans/${index}${from > 1 ? `?from=${from}` : ''}`);
74
+ }
75
+ /** TerritoryDetailView: the files of one code territory. */
76
+ export async function getTerritory(projectId, territoryId) {
77
+ return read(`/api/projects/${pid(projectId)}/code/territories/${pid(territoryId)}`);
78
+ }
79
+ /** RoundView[], newest first (§6.9). */
80
+ export async function getRounds(projectId) {
81
+ return read(`/api/projects/${pid(projectId)}/k-rounds`);
82
+ }
83
+ /** RoundDocView: one document a round left behind. */
84
+ export async function getRoundDoc(projectId, roundId, docId) {
85
+ return read(`/api/projects/${pid(projectId)}/k-rounds/${pid(roundId)}/docs/${pid(docId)}`);
86
+ }
87
+ /** ScopeKView: the ledger's coverage, layers, generations and the Project folder authorization (§6.7). */
88
+ export async function getScopeK(projectId) {
89
+ return read(`/api/projects/${pid(projectId)}/scope-k`);
90
+ }
91
+
92
+ // ── writes: the owner's `No action needed` (§1.18, §2.12) ─────────────────
93
+ /** The response lands on the cached view too, so a redraw shows it without a refetch. */
94
+ function applyBreakpointResponse(view, bid, reason, at) {
95
+ const bp = view?.breakpoints.find((b) => b.id === bid);
96
+ if (!bp) return null;
97
+ Object.assign(bp, { lit: false, ownerResponse: { reason, at } });
98
+ return bp;
99
+ }
100
+
101
+ export async function respondBreakpoint(projectId, bid, reason) {
102
+ const bp = await api(`/api/projects/${pid(projectId)}/breakpoints/${pid(bid)}/response`, { method: 'POST', body: { reason } });
103
+ applyBreakpointResponse(await getProcess(projectId), bid, reason, bp?.ownerResponse?.at ?? new Date().toISOString());
104
+ return bp;
105
+ }
106
+
107
+ export async function respondSendBack(projectId, sid, reason) {
108
+ const sb = await api(`/api/projects/${pid(projectId)}/sendbacks/${pid(sid)}/response`, { method: 'POST', body: { reason } });
109
+ const view = await getProcess(projectId);
110
+ const cached = view?.sendBacks.find((s) => s.id === sid);
111
+ if (cached) Object.assign(cached, { lit: false, ownerResponse: sb?.ownerResponse ?? { reason, at: new Date().toISOString() } });
112
+ return sb;
113
+ }
114
+
115
+ /** Assets changed: the next read fetches again. */
116
+ export function invalidate() {
117
+ missing.clear();
118
+ }
package/ui/k-fold.js ADDED
@@ -0,0 +1,106 @@
1
+ // The fold rule of the process view (Spec §6.3, §2.12; CKC-24 AC-11, AC-12; D75; E152, E153), and what a folded work says.
2
+ //
3
+ // One rule for the List and the Graph: every work's process is folded until the owner opens it, and what the owner
4
+ // opened or folded is one choice, the same on both. The Graph's steps make the picture long (owner, 2026-10-01, E152:
5
+ // 「页面上work item相应的QC之类的目前都展开了,能收进去么。这个会把页面撑大」), and so do the List's (E153: 「List也默认收起,一样」).
6
+ // What needs the owner stays on a folded work: its open send-backs and lit breakpoints hang off it, and the count of
7
+ // what is open stands on it (`openMark`) — on the Graph on its top edge, in the List on its row.
8
+ //
9
+ // No DOM here: data in, words out, so the rule is tested with `node --test` (src/ui/k-fold.test.ts). `getProc` gives
10
+ // the process view as it stands (src/model/views-k.ts `ProcessView`), or null before it has loaded.
11
+
12
+ const plural = (k, word) => `${k} ${word}${k === 1 ? '' : 's'}`;
13
+
14
+ /** A fix or a check is a step of the work it serves (WorkProcessView.stepOf): not a unit of its own anywhere. */
15
+ export const stepOfIn = (proc, id) => proc?.works?.[id]?.stepOf ?? null;
16
+ /** A work whose process can be opened: more than one step, or a plan with an execution shape. */
17
+ export const expandableIn = (proc, id) => Boolean(proc && !stepOfIn(proc, id) && ((proc.works?.[id]?.steps.length ?? 0) > 1 || proc.plans?.[id]));
18
+
19
+ export function createFold(getProc) {
20
+ const chosen = new Map(); // work or plan id → open (true) or folded (false), as the owner left it
21
+ const expandable = (id) => expandableIn(getProc(), id);
22
+ const ids = () => Object.keys(getProc()?.works ?? {}).filter(expandable);
23
+ /** Whether a work's process is open, in the List and on the Graph alike: the owner's choice, else folded. */
24
+ const isOpen = (id) => chosen.get(id) === true;
25
+ return {
26
+ has: isOpen,
27
+ expandable,
28
+ any: () => ids().length > 0,
29
+ anyOpen: () => ids().some(isOpen),
30
+ allExpanded: () => Boolean(getProc()) && ids().length > 0 && ids().every(isOpen),
31
+ toggle(id) { chosen.set(id, !isOpen(id)); },
32
+ /** A work opened or folded by hand, to the state given (a List row tells its own state when it toggles). */
33
+ set(id, open) { chosen.set(id, Boolean(open)); },
34
+ /** `Expand all` opens every work, `Fold all` folds every one, on both surfaces. */
35
+ setAll(on) {
36
+ if (!getProc()) return;
37
+ if (on) { for (const id of ids()) chosen.set(id, true); } else { chosen.clear(); }
38
+ },
39
+ reset() { chosen.clear(); },
40
+ };
41
+ }
42
+
43
+ /**
44
+ * The second line of a work on the Graph. A work whose process can be opened says how much is folded under it and
45
+ * whether anything in it is open — `5 steps · all closed`, `7 steps · ⚠ 2 open` — then its latest check and its
46
+ * execution (its progress is on the line above). A work with one step or none keeps its four things (§2.12). Null for a
47
+ * fix or a check, which folds under the work it serves.
48
+ */
49
+ export function foldLine(proc, id) {
50
+ const w = proc?.works?.[id];
51
+ if (!w || w.stepOf) return null;
52
+ const four = w.four;
53
+ const open = four.open.findings + four.open.sendBacks + four.open.breakpoints;
54
+ const check = four.check && four.check !== 'Not checked' ? four.check.verdict : four.check === 'Not checked' ? 'Not checked' : null;
55
+ const execution = four.execution !== four.progress ? four.execution : null;
56
+ if (expandableIn(proc, id)) {
57
+ const shape = proc.plans?.[id];
58
+ const size = w.steps.length ? plural(w.steps.length, 'step') : shape ? `${shape.batches.length} batch${shape.batches.length === 1 ? '' : 'es'}` : null;
59
+ return [size, open ? `⚠ ${open} open` : 'all closed', check, execution].filter(Boolean).join(' · ');
60
+ }
61
+ return [four.progress, execution, check, open ? `⚠ ${open} open` : null].filter(Boolean).join(' · ');
62
+ }
63
+
64
+ const openText = (sum) => [sum.sendBacks ? `${plural(sum.sendBacks, 'send-back')} open` : null, sum.breakpoints ? `${plural(sum.breakpoints, 'breakpoint')} lit` : null, sum.findings ? `${plural(sum.findings, 'finding')} still open` : null].filter(Boolean).join(' · ');
65
+ /** What a work has open — its own and that of the fixes and checks folded under it — added onto `sum`. */
66
+ function addOpenOf(proc, id, sum) {
67
+ const w = proc?.works?.[id];
68
+ if (!w || w.stepOf) return;
69
+ for (const c of [w, ...Object.values(proc.works).filter((x) => x.stepOf === id)]) { sum.sendBacks += c.four.open.sendBacks; sum.breakpoints += c.four.open.breakpoints; sum.findings += c.four.open.findings; }
70
+ }
71
+
72
+ /**
73
+ * What is open on a work, for the count on it, folded or not (D75; E152, E153) — on the Graph on its top edge, in the
74
+ * List on its row: its open send-backs, its lit breakpoints, its findings still open — its own and those of the fixes
75
+ * and checks folded under it. `{ count, text }`, or null when nothing is open.
76
+ */
77
+ export function openMark(proc, id) {
78
+ const sum = { sendBacks: 0, breakpoints: 0, findings: 0 };
79
+ addOpenOf(proc, id, sum);
80
+ const count = sum.sendBacks + sum.breakpoints + sum.findings;
81
+ return count ? { count, text: openText(sum) } : null;
82
+ }
83
+
84
+ /**
85
+ * What is still open inside a folded block of the List, for its head (D75 「折起来也亮着」; Spec §6.3 List, CKC-09
86
+ * AC-15; E153): over the work the block draws (`works` — each as `openMark` counts it) and on the other objects it
87
+ * holds (`objects` — a requirement, a decision, the area itself: the open send-backs and lit breakpoints hanging on
88
+ * them). An id named twice counts once. `{ count, text }`, or null when nothing inside is open.
89
+ */
90
+ export function openInside(proc, { works = [], objects = [] } = {}) {
91
+ if (!proc) return null;
92
+ const sum = { sendBacks: 0, breakpoints: 0, findings: 0 };
93
+ const seen = new Set();
94
+ for (const id of works) { if (seen.has(id)) continue; seen.add(id); addOpenOf(proc, id, sum); }
95
+ // A fix or a check folded under a work the block draws is counted with that work.
96
+ const under = new Set(Object.entries(proc.works ?? {}).filter(([, w]) => w.stepOf && seen.has(w.stepOf)).map(([id]) => id));
97
+ for (const id of objects) {
98
+ if (seen.has(id) || under.has(id)) continue;
99
+ seen.add(id);
100
+ if (proc.works?.[id]) continue; // a work the block does not draw as a row is not counted through its objects
101
+ sum.sendBacks += (proc.sendBacks ?? []).filter((x) => x.targetId === id && x.lit && x.stage !== 'Closed').length;
102
+ sum.breakpoints += (proc.breakpoints ?? []).filter((x) => x.targetId === id && x.lit).length;
103
+ }
104
+ const count = sum.sendBacks + sum.breakpoints + sum.findings;
105
+ return count ? { count, text: openText(sum) } : null;
106
+ }
@@ -0,0 +1,302 @@
1
+ /* The process view of increment K (CKC-24; mockups list-process-v1.html and graph-process-v0.html), the popover's
2
+ `How it got here` and versions (§6.4), the generation bands (D85), and the top bar's breakpoint count. Only the
3
+ themes' own variables are used (Spec §6.16); the fallbacks are the factory ones. */
4
+
5
+ /* ── A work in the List: what was done on the left, what reality gave on the right ── */
6
+ .unit{border-bottom:1px dashed var(--pk-line-soft,#2b2b26);padding:3px 0}
7
+ .unit:last-child{border-bottom:0}
8
+ .row2{display:grid;grid-template-columns:1fr 1fr;column-gap:16px;align-items:start}
9
+ details.unit>summary{list-style:none;cursor:pointer}
10
+ details.unit>summary::-webkit-details-marker{display:none}
11
+ .caret{display:inline-block;width:14px;color:var(--pk-text-faint,#85857b);font-size:11px}
12
+ details.unit[open]>summary .caret::before{content:'▾'}
13
+ details.unit:not([open])>summary .caret::before{content:'▸'}
14
+ .nocaret{display:inline-block;width:14px}
15
+ .sumline{font-size:11.5px;color:var(--pk-text-faint,#85857b);padding-left:14px;margin-top:1px}
16
+ /* What is still open lights up, folded or not (D75). */
17
+ .flag{font-size:11.5px;padding-left:14px;margin-top:2px;line-height:1.6}
18
+ .flag.back{color:var(--pk-red,#dca6a0)}
19
+ .flag.up{color:var(--pk-accent,#e3b76f)}
20
+ .steps{margin:4px 0 4px 6px;border-left:1px solid var(--pk-line,#35352f);padding-left:10px;display:flex;flex-direction:column;gap:3px}
21
+ .step .k{display:inline-block;min-width:74px;color:var(--pk-text-faint,#85857b);font-size:11px}
22
+ .step .who{color:var(--pk-text,#e7e7df)}
23
+ .step .say{color:var(--pk-text-muted,#a5a59b);font-size:11.5px}
24
+ .step.history .say{text-decoration:line-through}
25
+ .route{font-size:11px;white-space:nowrap;margin-left:4px}
26
+ .route.back{color:var(--pk-red,#dca6a0)}
27
+ .route.up{color:var(--pk-accent,#e3b76f)}
28
+ .route.closed{color:var(--pk-green,#a3c5ac)}
29
+ .route.hand{color:var(--pk-blue,#9fbdd5)}
30
+ .sub{margin-left:18px;border-left:1px dashed var(--pk-line,#35352f);padding-left:10px}
31
+ .par{margin:2px 0 2px 18px;border-left:2px solid var(--pk-blue,#9fbdd5);padding-left:10px;display:flex;flex-direction:column;gap:3px}
32
+ .par-head{font-size:11px;color:var(--pk-blue,#9fbdd5)}
33
+ .order{font-size:11.5px;color:var(--pk-text-muted,#a5a59b);padding-left:14px;margin:2px 0 4px}
34
+ .order b{color:var(--pk-text,#e7e7df);font-weight:600}
35
+ .order .moved{color:var(--pk-accent,#e3b76f)}
36
+ .intent-flag{margin-top:6px;font-size:11.5px;color:var(--pk-accent,#e3b76f);line-height:1.6}
37
+ .ptr{font-size:11.5px;color:var(--pk-text-faint,#85857b)}
38
+ .text-btn.copy{font-size:11px;margin-left:6px;text-decoration:underline dotted;color:var(--pk-accent,#e3b76f)}
39
+ .faint-num{color:var(--pk-text-faint,#85857b)}
40
+
41
+ /* ── The List by module (D100; the owner's drawn structure): a block per area, the ringed cross-cutting block last ── */
42
+ .list-wrap.by-module{padding:0 12px 14px;gap:10px}
43
+ .list-wrap.by-module>.goals{padding:6px 0 0}
44
+ /* A module's body is a panel of its own (owner 2026-09-30: a1 draws it as WorkflowLens's dark striped wood). */
45
+ .ablock{border:1px solid var(--pk-module-border,var(--pk-section-border,var(--pk-line-soft,#2b2b26)));border-radius:var(--pk-section-radius,var(--pk-radius,6px));background:var(--pk-module-bg,var(--pk-section-bg,var(--pk-surface,#1c1c1a)));background-image:var(--pk-module-texture,none);box-shadow:var(--pk-module-shadow,none);padding:12px 14px 12px;display:flex;flex-direction:column;gap:5px;font-size:12px}
46
+ .ablock.sel{box-shadow:inset 3px 0 0 var(--pk-accent,#e3b76f)}
47
+ /* The cross-cutting block is ringed like the Graph's ring (Spec §6.3): a solid line with round corners, never dashes. */
48
+ .ablock.cross{border:1.5px solid var(--pk-graph-intent,var(--pk-blue,#9fbdd5));border-radius:22px;padding:12px 16px}
49
+ .ablock>.ab-head{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
50
+ .ablock>.ab-head h3{font-size:13.5px;font-weight:600;margin:0}
51
+ .ablock>.ab-head h3 .text-btn{font-size:13.5px;font-weight:600;color:var(--pk-text-strong,var(--pk-text,#e7e7df))}
52
+ .intent-basis>summary{cursor:pointer;font-size:11.5px;color:var(--pk-blue,#9fbdd5);list-style:none}
53
+ .intent-basis>summary::-webkit-details-marker{display:none}
54
+ .intent-basis[open]>summary{text-decoration:underline dotted}
55
+ .intent-basis .ib-body{margin:4px 0 2px;padding:6px 10px;border-left:2px solid var(--pk-line,#35352f);display:flex;flex-direction:column;gap:6px;color:var(--pk-text-muted,#a5a59b);max-width:900px}
56
+ .ablock .effect{margin:0 0 2px;color:var(--pk-text-muted,#a5a59b)}
57
+ .ablock .rddrow{display:flex;gap:8px;align-items:flex-start;flex-wrap:wrap;margin-bottom:4px}
58
+ .ablock .rdd{margin-top:0;padding:2px 8px;border:1px solid var(--pk-line-soft,#2b2b26);border-radius:var(--pk-radius-sm,5px)}
59
+ .ablock .rdd[open]{flex:1 1 100%}
60
+ /* Inside the fold one item a line, with room; the version line of its document in a smaller, quieter tone. */
61
+ .ablock .rdd>div{padding:2px 0;line-height:1.55}
62
+ .ablock .rdd>div>.faint{font-size:11px}
63
+ .ablock .cols{display:grid;grid-template-columns:1fr 1fr;column-gap:16px;color:var(--pk-text-faint,#85857b);font-size:11px;border-bottom:1px solid var(--pk-line,#35352f);padding:4px 0 3px 14px}
64
+ .ablock .sub-h{margin:8px 0 0;font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--pk-text-faint,#85857b)}
65
+ .ablock .pad{padding:4px 14px}
66
+ .wrow .wr-right .say{color:var(--pk-text-muted,#a5a59b)}
67
+ .wrow .proc-head{margin:4px 0 0 6px;padding-left:10px;font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;color:var(--pk-text-faint,#85857b)}
68
+ /* The tags on a work row are one kind of chip (owner 2026-09-30): the same size and a 1px frame, the plan's in the plan
69
+ colour, a shared item's in the work colour — solid in its main module, dashed where it is a copy. */
70
+ .wrow .tag{font-size:10.5px;line-height:1.5;padding:0 6px;border:1px solid var(--pk-line-soft,#2b2b26);box-shadow:none;vertical-align:1px}
71
+ .plan-tag{font-size:10.5px;border:1px solid var(--pk-graph-plan,var(--pk-line,#35352f))}
72
+ .plan-tag.none{color:var(--pk-text-faint,#85857b)}
73
+ /* A work item serving several modules (owner, 2026-09-30): its main row solid, its row in each other module dashed —
74
+ and faded as a whole, like its copy on the Graph (owner, 2026-10-01, E152: the dashes alone did not tell the two
75
+ apart). Pointed at, focused or selected, the row comes forward to be read; it stays dashed. */
76
+ .shared-tag{font-size:10.5px;border:1px solid var(--pk-graph-work,var(--pk-green,#a3c5ac))}
77
+ .shared-tag.main{border-style:solid}
78
+ .shared-tag.copy{border-style:dashed;color:var(--pk-text-muted,#a5a59b)}
79
+ .wrow.shared-copy{border:1px dashed var(--pk-graph-work,var(--pk-green,#a3c5ac));border-radius:var(--pk-radius-sm,5px);padding:3px 6px;margin:2px 0;opacity:.5;transition:opacity .12s}
80
+ .wrow.shared-copy:hover,.wrow.shared-copy:focus-within,.wrow.shared-copy[open]{opacity:.95}
81
+ .wrow.shared-copy:has(.sel){opacity:.9}
82
+ /* Work for a whole plan (CN): its tag in the plan's colour, the Keeper's reason under its name. */
83
+ .whole-tag{font-size:10.5px;border:1px solid var(--pk-graph-plan,var(--pk-line,#35352f))}
84
+ .basis-btn{font-size:11px;color:var(--pk-blue,#9fbdd5);text-decoration:underline dotted;margin-left:4px}
85
+ .obs-fold{margin-top:6px}
86
+ .obs-fold>summary{color:var(--pk-text-muted,#a5a59b)}
87
+ .plan-row{border-bottom:1px dashed var(--pk-line-soft,#2b2b26);padding:3px 0}
88
+ /* ── The List's layers (owner 2026-09-30, from a design study of hers: 「list画面更有层次一点,信息密度太大了」). The
89
+ top of the List is the Sign in card — a framed plate with an inlaid hairline and four rivets —, a module's head its
90
+ lane card — corner brackets, a small caps kicker, the title —, a module's body its own panel (above). Every part
91
+ reads its own tokens, which fall back to the section, the raised face and the accent, so each theme has a version
92
+ of it (TOKENS.md "The List's layers"). ── */
93
+ .list-plate{position:relative;display:flex;flex-direction:column;gap:14px;margin-top:10px;padding:22px 32px 22px;border:var(--pk-plate-border-width,1px) solid var(--pk-plate-border,var(--pk-section-border,var(--pk-line-soft,#2b2b26)));border-radius:var(--pk-plate-radius,var(--pk-radius,6px));background:var(--pk-plate-bg,var(--pk-section-bg,var(--pk-surface,#1c1c1a)));background-image:var(--pk-plate-texture,none);box-shadow:var(--pk-plate-shadow,var(--pk-section-shadow,none));font-size:12.5px}
94
+ .list-plate::before{content:'';position:absolute;inset:8px;pointer-events:none;border:1px solid var(--pk-plate-inlay,var(--pk-line-soft,#2b2b26));border-radius:var(--pk-radius-xs,3px)}
95
+ .list-plate>.rivet{position:absolute;width:8px;height:8px;border-radius:50%;background:var(--pk-plate-rivet,var(--pk-line-strong,#4b4b42));box-shadow:var(--pk-plate-rivet-shadow,none);pointer-events:none}
96
+ .list-plate>.rivet.r1{top:15px;left:16px}.list-plate>.rivet.r2{top:15px;right:16px}.list-plate>.rivet.r3{bottom:15px;left:16px}.list-plate>.rivet.r4{bottom:15px;right:16px}
97
+ .list-plate .plate-title{margin:0;text-align:center;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.38em;text-indent:.38em;color:var(--pk-plate-kicker,var(--pk-accent,#e3b76f))}
98
+ .list-plate .plate-label{display:block;margin:0 0 7px;font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.14em;color:var(--pk-plate-kicker,var(--pk-accent,#e3b76f))}
99
+ .list-plate .plate-count{font:600 10.5px var(--pk-font-mono,'Cascadia Code',Consolas,monospace);letter-spacing:0;color:var(--pk-text-faint,#85857b)}
100
+ .list-wrap .list-plate .plate-group{position:relative;display:flex;flex-direction:column;flex-wrap:nowrap;gap:0;padding:0}
101
+ .list-plate .words-fold{margin:0}
102
+ .list-plate .words-fold>summary{cursor:pointer;list-style:none;display:flex;align-items:baseline;gap:6px;font-weight:400}
103
+ .list-plate .words-fold>summary::-webkit-details-marker{display:none}
104
+ .list-plate .words-fold>summary::before{content:'▸';font-size:10px;color:var(--pk-plate-kicker,var(--pk-accent,#e3b76f))}
105
+ .list-plate .words-fold[open]>summary::before{content:'▾'}
106
+ .list-plate .words-fold>summary .plate-label{display:inline;margin:0}
107
+ .list-plate .words-fold>div{padding:3px 0 3px 16px;line-height:1.55}
108
+ .list-plate .words-fold[open]>summary{margin-bottom:5px}
109
+ .list-plate .plate-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
110
+ .list-plate .plate-slot{display:inline-flex;align-items:center;gap:6px;min-height:36px;padding:4px 13px;text-align:left;font-size:14px;font-weight:700;line-height:1.35;background:var(--pk-slot-bg,var(--pk-well-bg,var(--pk-surface,#1c1c1a)));color:var(--pk-slot-text,var(--pk-text,#e7e7df));border:1px solid var(--pk-slot-border,var(--pk-well-border,var(--pk-line,#35352f)));border-radius:var(--pk-radius-sm,4px);box-shadow:var(--pk-slot-shadow,var(--pk-well-shadow,none))}
111
+ .list-plate .plate-slot:hover{border-color:var(--pk-accent,#e3b76f)}
112
+ .list-plate .plate-slot.sel{outline:1px solid var(--pk-accent,#e3b76f);outline-offset:2px}
113
+ .list-plate .plate-row>.faint{font-size:11.5px}
114
+ .list-plate .goal-list{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:7px;counter-reset:goal}
115
+ .list-plate .goal-list>li{position:relative;padding-left:24px;line-height:1.6;counter-increment:goal}
116
+ .list-plate .goal-list>li::before{content:counter(goal);position:absolute;left:0;top:0;min-width:16px;font:600 10.5px/1.95 var(--pk-font-mono,'Cascadia Code',Consolas,monospace);color:var(--pk-plate-kicker,var(--pk-accent,#e3b76f))}
117
+ .list-plate .goal-line{padding:0;text-align:left;color:var(--pk-text,#e7e7df)}
118
+ .list-plate .goal-line:hover{text-decoration:underline dotted;text-underline-offset:3px}
119
+ .list-plate .goal-line.sel{color:var(--pk-accent,#e3b76f)}
120
+ /* The lane card: a module's code and counts in small caps, its name, the one sentence of its effect. */
121
+ .ablock>.ab-head.lane{position:relative;display:flex;flex-direction:column;align-items:stretch;gap:3px;margin:0 0 6px;padding:11px 18px 12px;background:var(--pk-lane-bg,var(--pk-raised,#242420));background-image:var(--pk-lane-texture,none);border:1px solid var(--pk-lane-border,var(--pk-line,#35352f));border-radius:var(--pk-lane-radius,var(--pk-radius-sm,4px));box-shadow:var(--pk-lane-shadow,none)}
122
+ .ablock>.ab-head.lane::before{content:'';position:absolute;inset:5px;pointer-events:none;background-image:linear-gradient(var(--pk-lane-corner,var(--pk-accent,#e3b76f)),var(--pk-lane-corner,var(--pk-accent,#e3b76f))),linear-gradient(var(--pk-lane-corner,var(--pk-accent,#e3b76f)),var(--pk-lane-corner,var(--pk-accent,#e3b76f))),linear-gradient(var(--pk-lane-corner,var(--pk-accent,#e3b76f)),var(--pk-lane-corner,var(--pk-accent,#e3b76f))),linear-gradient(var(--pk-lane-corner,var(--pk-accent,#e3b76f)),var(--pk-lane-corner,var(--pk-accent,#e3b76f))),linear-gradient(var(--pk-lane-corner,var(--pk-accent,#e3b76f)),var(--pk-lane-corner,var(--pk-accent,#e3b76f))),linear-gradient(var(--pk-lane-corner,var(--pk-accent,#e3b76f)),var(--pk-lane-corner,var(--pk-accent,#e3b76f))),linear-gradient(var(--pk-lane-corner,var(--pk-accent,#e3b76f)),var(--pk-lane-corner,var(--pk-accent,#e3b76f))),linear-gradient(var(--pk-lane-corner,var(--pk-accent,#e3b76f)),var(--pk-lane-corner,var(--pk-accent,#e3b76f)));background-size:14px 2px,2px 14px,14px 2px,2px 14px,14px 2px,2px 14px,14px 2px,2px 14px;background-repeat:no-repeat;background-position:top left,top left,top right,top right,bottom left,bottom left,bottom right,bottom right}
123
+ .ab-head.lane .lane-top{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap}
124
+ .ab-head.lane .lane-kicker{font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--pk-lane-kicker,var(--pk-accent,#e3b76f))}
125
+ .ab-head.lane .lane-count{font-size:11px;color:var(--pk-lane-sub,var(--pk-text-muted,#a5a59b));font-variant-numeric:tabular-nums}
126
+ .ablock>.ab-head.lane h3.lane-title{margin:0;font-size:15px;font-weight:700;line-height:1.35;color:var(--pk-lane-title,var(--pk-text-strong,var(--pk-text,#e7e7df)))}
127
+ .ablock>.ab-head.lane h3.lane-title .text-btn{padding:0;font-size:inherit;font-weight:inherit;font-family:inherit;color:inherit}
128
+ .ablock>.ab-head.lane h3.lane-title .text-btn:hover{text-decoration:underline dotted;text-underline-offset:3px}
129
+ .ablock .ab-head.lane .lane-sub{margin:1px 0 0;font-size:12px;line-height:1.5;font-style:italic;color:var(--pk-lane-sub,var(--pk-text-muted,#a5a59b))}
130
+ .ab-head.lane .intent-basis{align-self:flex-start;margin-top:2px}
131
+ .ab-head.lane .intent-basis>summary{font-size:11px;color:var(--pk-lane-link,var(--pk-blue,#9fbdd5))}
132
+ .ab-head.lane .intent-basis .ib-body{color:var(--pk-lane-sub,var(--pk-text-muted,#a5a59b));border-left-color:var(--pk-lane-corner,var(--pk-line,#35352f))}
133
+ /* Compact rows (owner, 2026-10-01, E153: 「现在的布局太松散了,左边也是两行右边也是,但是一共三行,整体看起来非常长」). A folded
134
+ row is one line on each side — the name, its tags, the count of what is open and what is folded under it; the one
135
+ sentence cut to the line (the whole of it in the hover) and `Basis` — with one separator between rows and no blank
136
+ line. In a narrow row the steps note gives way first, then the name; tags and marks are never cut. Opened, the
137
+ sentence is whole. */
138
+ .ablock .cols{padding:8px 0 5px 14px;font-size:10.5px;text-transform:uppercase;letter-spacing:.06em}
139
+ .ablock .rows-note{padding:5px 0 5px 14px;font-size:11px;line-height:1.5;color:var(--pk-text-faint,#85857b);border-bottom:1px solid var(--pk-line-soft,#2b2b26)}
140
+ .ablock .rows-note b{font-weight:600;color:var(--pk-text-muted,#a5a59b)}
141
+ .wrow{padding:6px 0}
142
+ .ablock .wrow{border-bottom:1px solid var(--pk-line-soft,#2b2b26)}
143
+ .wrow .row2{grid-template-columns:minmax(0,1fr) minmax(0,1fr);column-gap:28px;align-items:center}
144
+ .wrow .wr-left,.wrow .wr-right{display:flex;align-items:center;gap:6px;min-width:0;white-space:nowrap;line-height:20px}
145
+ .wrow .wr-left>*,.wrow .wr-right>*{flex:none}
146
+ .wrow .wr-left>.caret,.wrow .wr-left>.nocaret{margin-right:-6px}
147
+ .wrow .wr-left>.wr-name{flex:0 1 auto;min-width:0;display:flex;align-items:center}
148
+ .wrow .wr-name>span:not(.text-btn){display:flex;align-items:center;min-width:0}
149
+ .wrow .wr-name .text-btn{display:block;min-width:0;padding:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:20px}
150
+ .wrow .wr-name .star,.wrow .wr-name .q-mark{flex:none}
151
+ .wr-steps,.wr-why{font-size:11px;color:var(--pk-text-faint,#85857b)}
152
+ .wr-steps.same{font-style:italic}
153
+ .wrow .wr-left>.wr-steps,.wrow .wr-left>.wr-why{flex:0 1000 auto;min-width:0;overflow:hidden;text-overflow:ellipsis}
154
+ .wrow .wr-right>.say{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;font-size:11.5px;color:var(--pk-text-muted,#a5a59b)}
155
+ .wrow .wr-right>.basis-btn{margin-left:0;padding:0;line-height:20px}
156
+ details.wrow[open]>summary .row2{align-items:start}
157
+ details.wrow[open]>summary .wr-right{white-space:normal;align-items:baseline}
158
+ details.wrow[open]>summary .wr-right>.say{overflow:visible}
159
+ .wrow .flag{margin-top:1px}
160
+ .ablock .wrow.shared-copy{padding:5px 8px;margin:2px 0}
161
+ /* ── The List's parts open and close (E153; ui/list-fold.js): the top plate, a module's block, the foundation's, the
162
+ ring's. Each is folded to its head until the owner opens it; a press on the head opens or folds it where it stands.
163
+ Folded, a block is its head card alone, on two lines — code, name, what is open inside and its counts; the effect it
164
+ is for — and the plate is one line with the product's name. ── */
165
+ /* The body's parts keep the room they had between them; the work rows stand one under the other with no gap. */
166
+ .ab-body{display:flex;flex-direction:column}
167
+ .ab-body>*+*{margin-top:5px}
168
+ .ab-body>.wrow{margin-top:0}
169
+ .ab-body[hidden],.plate-body[hidden]{display:none}
170
+ [data-fold-head]{cursor:pointer}
171
+ [data-fold-head]:focus-visible{outline:2px solid var(--pk-accent,#e3b76f);outline-offset:2px}
172
+ .fold-caret{display:inline-block;flex:none;width:12px;font-size:10px;line-height:1;color:var(--pk-lane-kicker,var(--pk-accent,#e3b76f))}
173
+ .fold-caret::before{content:'▸'}
174
+ [data-open="true"]>[data-fold-head] .fold-caret::before{content:'▾'}
175
+ .ab-head.lane .lane-top{justify-content:flex-start;gap:5px 6px}
176
+ .ab-head.lane .lane-right{display:inline-flex;align-items:center;gap:8px;margin-left:auto}
177
+ .lane-right .tag,.plate-holds .tag,details.genband>summary .tag.inside-open{font-size:10.5px;line-height:1.5;padding:0 6px}
178
+ .lane-right .q-mark,.plate-holds .q-mark{margin-left:0}
179
+ .ablock[data-open="false"]{padding:0;gap:0;border-color:transparent;background:none;box-shadow:none}
180
+ .ablock[data-open="false"]>.ab-head.lane{margin:0;display:grid;grid-template-columns:auto auto minmax(0,1fr) auto;column-gap:10px;row-gap:2px;align-items:baseline;padding:8px 18px 9px}
181
+ .ablock[data-open="false"]>.ab-head.lane>.lane-top{display:contents}
182
+ .ablock[data-open="false"]>.ab-head.lane .fold-caret{grid-area:1/1}
183
+ .ablock[data-open="false"]>.ab-head.lane .lane-kicker{grid-area:1/2}
184
+ .ablock[data-open="false"]>.ab-head.lane>h3.lane-title{grid-area:1/3;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
185
+ .ablock[data-open="false"]>.ab-head.lane .lane-right{grid-area:1/4;margin-left:0}
186
+ .ablock[data-open="false"]>.ab-head.lane>.lane-sub{grid-area:2/2/3/-1;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
187
+ .ablock[data-open="false"]>.ab-head.lane>.intent-basis{display:none}
188
+ .ablock.sel[data-open="false"]>.ab-head.lane{outline:1px solid var(--pk-accent,#e3b76f);outline-offset:1px}
189
+ /* The ring keeps its ring folded; its head says its name first, then that it is not one-to-one with the modules. */
190
+ .ablock.cross[data-open="false"]{padding:5px 7px;border:1.5px solid var(--pk-graph-intent,var(--pk-blue,#9fbdd5))}
191
+ .ablock.cross[data-open="false"]>.ab-head.lane{border-radius:16px}
192
+ .ablock.cross[data-open="false"]>.ab-head.lane>h3.lane-title{grid-area:1/2}
193
+ .ablock.cross[data-open="false"]>.ab-head.lane .lane-kicker{grid-area:1/3;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
194
+ .plate-head{position:relative;display:flex;align-items:baseline;justify-content:center;gap:8px}
195
+ .plate-head>.plate-brief,.plate-head>.plate-holds{display:none}
196
+ .plate-body{display:flex;flex-direction:column;gap:14px}
197
+ .list-plate[data-open="false"]{padding:8px 18px;gap:0}
198
+ .list-plate[data-open="false"]::before,.list-plate[data-open="false"]>.rivet{display:none}
199
+ .list-plate[data-open="false"]>.plate-head{justify-content:flex-start;gap:12px}
200
+ .list-plate[data-open="false"]>.plate-head>.plate-title{flex:none}
201
+ .list-plate[data-open="false"]>.plate-head>.plate-brief{display:block;flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13.5px;font-weight:700;color:var(--pk-text-strong,var(--pk-text,#e7e7df))}
202
+ .list-plate[data-open="false"]>.plate-head>.plate-holds{display:inline-flex;align-items:center;gap:8px;flex:none;margin-left:auto;font-size:11px;color:var(--pk-text-muted,#a5a59b);font-variant-numeric:tabular-nums}
203
+ .list-only{padding:6px 10px;border:1px solid var(--pk-accent,#e3b76f);border-radius:var(--pk-radius-sm,5px);color:var(--pk-text,#e7e7df);font-size:12px}
204
+ .gendest{color:var(--pk-text-muted,#a5a59b)}
205
+ /* ❓ on an object (D100): lit while a note on it needs you, quiet otherwise; the same mark as on the Graph. */
206
+ .q-mark{display:inline-block;margin-left:4px;padding:0 5px;border-radius:9px;font-size:10.5px;line-height:16px;vertical-align:1px}
207
+ .q-mark.ask{background:var(--pk-accent,#e3b76f);color:var(--pk-ground,#141412);font-weight:700}
208
+ .q-mark.note{border:1px solid var(--pk-line,#35352f);opacity:.6}
209
+
210
+ /* ── Earlier generations (D85): one rolled-up band per generation, before the current plan ── */
211
+ .genbands{display:flex;flex-direction:column;gap:6px;padding:4px 0}
212
+ details.genband{border:1px solid var(--pk-section-border,var(--pk-line-soft,#2b2b26));border-radius:var(--pk-section-radius,var(--pk-radius-sm,5px));background:var(--pk-section-bg,var(--pk-surface,#1c1c1a))}
213
+ details.genband>summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding:7px 10px;font-size:12px;color:var(--pk-text-muted,#a5a59b)}
214
+ details.genband>summary::-webkit-details-marker{display:none}
215
+ details.genband>summary::before{content:'▸';font-size:10px;color:var(--pk-text-faint,#85857b)}
216
+ details.genband[open]>summary::before{content:'▾'}
217
+ .genname{font-weight:600;color:var(--pk-text,#e7e7df)}
218
+ .genbody{padding:2px 10px 10px;display:flex;flex-direction:column}
219
+
220
+ /* ── The popover and full details (§6.4): How it got here, the versions, what is open ── */
221
+ .kp-block{display:flex;flex-direction:column;gap:5px}
222
+ .kp-block>h4{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--pk-text-faint,#85857b)}
223
+ .kp-lin,.kp-ver{font-size:12px;line-height:1.6}
224
+ .kp-lin .kp-when,.kp-ver .kp-when{color:var(--pk-text-muted,#a5a59b);font-variant-numeric:tabular-nums;white-space:nowrap}
225
+ .kp-lin .kp-what{color:var(--pk-text,#e7e7df)}
226
+ .kp-lin.history .kp-what{color:var(--pk-text-faint,#85857b);text-decoration:line-through}
227
+ .kp-ver.current b{color:var(--pk-text-strong,var(--pk-text,#e7e7df))}
228
+ .kp-ver.superseded s{color:var(--pk-text-faint,#85857b)}
229
+ .kp-sup{font-size:11px;color:var(--pk-text-muted,#a5a59b);padding-left:2px}
230
+ .kp-more summary{cursor:pointer;color:var(--pk-text-faint,#85857b);font-size:11.5px}
231
+ .kp-more summary:hover{color:var(--pk-text-muted,#a5a59b)}
232
+ .kp-more>.content{padding:4px 0 2px 12px;display:flex;flex-direction:column;gap:4px}
233
+ /* The history of the document an object sits in, set apart and named as that document's (owner 2026-09-30): a quiet
234
+ well under the object's own trail, folded in the popover. */
235
+ details.kp-doc-history{border:1px solid var(--pk-line-soft,#2b2b26);border-radius:var(--pk-radius-sm,5px);background:var(--pk-well-bg,var(--pk-surface,#1c1c1a));padding:6px 9px;gap:6px}
236
+ details.kp-doc-history>summary{cursor:pointer;display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 8px;list-style-position:outside}
237
+ details.kp-doc-history>summary .kp-doc-kicker{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--pk-text-faint,#85857b)}
238
+ details.kp-doc-history>summary .kp-doc-name{font:11.5px var(--pk-font-mono,'Cascadia Code',Consolas,monospace);color:var(--pk-text-muted,#a5a59b);overflow-wrap:anywhere}
239
+ details.kp-doc-history>summary::-webkit-details-marker{display:none}
240
+ details.kp-doc-history>summary::before{content:'▸';color:var(--pk-text-faint,#85857b);font-size:10px}
241
+ details.kp-doc-history[open]>summary::before{content:'▾'}
242
+ details.kp-doc-history[open]>summary{margin-bottom:4px}
243
+ details.kp-doc-history .kp-doc-steps{display:flex;flex-direction:column;gap:5px}
244
+ details.kp-doc-history .kp-block{padding-top:4px;border-top:1px dashed var(--pk-line-soft,#2b2b26)}
245
+ .kp-openitem{font-size:12px;line-height:1.7}
246
+
247
+ /* ── The top bar count (§6.2, §2.12) and its panel ── */
248
+ .kp-count{font-variant-numeric:tabular-nums;color:var(--pk-red,#dca6a0)}
249
+ .kp-counts-panel .kp-countrow{display:flex;align-items:baseline;gap:6px;padding:3px 0;font-size:12px;flex-wrap:wrap}
250
+ .kp-countrow .kp-what{color:var(--pk-text,#e7e7df);min-width:0}
251
+
252
+ /* ── The Code view's mount (§6.17); the renderer inside is AS's ui/k/code-view.js ── */
253
+ .k-code{flex:1;min-height:0;overflow:auto}
254
+
255
+ /* ── QC AY: what the owner reads beside an open item, a patch, an earlier plan, a session draft ── */
256
+ /* The evidence of a breakpoint or send-back, and the original line itself, verbatim (CKC-24 AC-3). */
257
+ .kp-evid{list-style:none;margin:2px 0 0;padding:0;display:flex;flex-direction:column;gap:3px;font-size:11.5px}
258
+ .kp-evid li{line-height:1.55;color:var(--pk-text-muted,#a5a59b)}
259
+ .kp-evid .kp-evid-label{color:var(--pk-text,#e7e7df)}
260
+ .kp-line{margin:2px 0 0;padding:2px 8px;border-left:2px solid var(--pk-accent,#e3b76f);color:var(--pk-text,#e7e7df);white-space:pre-wrap;overflow-wrap:anywhere}
261
+ .kp-openitem .kp-evid{margin:3px 0 2px 2px}
262
+ /* The flag's words open the item; they read as the flag they were (its colour, its size), not as a link. */
263
+ .flag .flag-open{display:inline;color:inherit;text-align:left;font:inherit;line-height:inherit;padding:0;background:none;border:0;cursor:pointer}
264
+ .flag .flag-open:hover{color:inherit;text-decoration:underline dotted}
265
+ .kp-lines-mark{color:var(--pk-text-faint,#85857b);font-size:11px}
266
+ .kp-countrow .kp-what.text-btn{color:var(--pk-text,#e7e7df);text-align:left}
267
+ /* A work's territories: the jump to Code (CKC-25 AC-7). */
268
+ .kp-code .row{gap:5px}
269
+ /* The versions list's pointer to the patch that replaced it (CKC-26 AC-4). */
270
+ .kp-patch-link{font-size:11px;color:var(--pk-accent,#e3b76f);text-decoration:underline dotted}
271
+ .kp-patch-four{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;margin:10px 0}
272
+ .kp-patch-four>div{display:flex;flex-direction:column;gap:2px;line-height:1.6}
273
+ .kp-patch-four s{color:var(--pk-text-faint,#85857b)}
274
+ .kp-patch-text{margin-top:6px}
275
+ /* An earlier generation's plans in its band (CKC-24 AC-18). */
276
+ .genplan{border-bottom:1px dashed var(--pk-line-soft,#2b2b26);padding:3px 0}
277
+ .gendocs{font-size:11.5px;padding:4px 0 4px 14px;display:flex;flex-wrap:wrap;gap:4px 10px;align-items:baseline}
278
+ .gendoc{font-size:11.5px;color:var(--pk-accent,#e3b76f);text-decoration:underline dotted}
279
+ .kp-deleted{color:var(--pk-text-muted,#a5a59b)}
280
+ .kp-plan-text{max-height:60vh}
281
+ /* A session's draft (§3.11; CKC-23 AC-18): the owner's lines verbatim with their kind, the agents' claims. */
282
+ .kd-sessions .kd-ref{font-size:12px;line-height:1.7}
283
+ .kd-open{font-size:12px;color:var(--pk-accent,#e3b76f)}
284
+ .kd-none{color:var(--pk-text-faint,#85857b)}
285
+ .kd-src-draft{font-size:11px;color:var(--pk-accent,#e3b76f);text-decoration:underline dotted}
286
+ .kd-meta{margin-bottom:8px}
287
+ .kd-h{margin:12px 0 6px;font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--pk-text-faint,#85857b)}
288
+ .kd-lines,.kd-claims{display:flex;flex-direction:column;gap:8px}
289
+ .kd-line{padding:6px 10px;border:1px solid var(--pk-well-border,var(--pk-line-soft,#2b2b26));border-radius:var(--pk-radius-sm,5px);background:var(--pk-well-bg,var(--pk-rail,#191918))}
290
+ .kd-line.kd-decision,.kd-line.kd-confirmation{border-left:3px solid var(--pk-accent,#e3b76f)}
291
+ .kd-line-head{display:flex;flex-wrap:wrap;gap:4px 8px;align-items:baseline;font-size:11px;color:var(--pk-text-muted,#a5a59b)}
292
+ .kd-when{font-variant-numeric:tabular-nums}
293
+ .kd-orig{font-size:11px;color:var(--pk-accent,#e3b76f)}
294
+ .kd-text{margin-top:4px;white-space:pre-wrap;overflow-wrap:anywhere;color:var(--pk-text,#e7e7df);font-size:12.5px;line-height:1.65;max-height:40vh;overflow:auto}
295
+ .kd-confirm{margin-top:6px;display:flex;flex-direction:column;gap:4px;font-size:12px}
296
+ .kd-answers{margin:2px 0 0;white-space:pre-wrap;overflow-wrap:anywhere;max-height:30vh;overflow:auto;font-style:normal}
297
+ .kd-confirms{color:var(--pk-text,#e7e7df)}
298
+ .kd-claim{font-size:12px;line-height:1.6}
299
+ .kd-claim-text{color:var(--pk-text-muted,#a5a59b);padding-left:2px}
300
+ .kd-foot{margin-top:12px;font-size:11px}
301
+ /* Change log: a semantic patch is its own kind of row (CKC-26 AC-4). */
302
+ .tl-patch .tl-patch-link{color:var(--pk-text,#e7e7df);text-align:left}