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.
- package/CHANGELOG.md +56 -0
- package/LICENSE +21 -0
- package/README.md +283 -2
- package/README.zh-CN.md +285 -0
- package/THIRD_PARTY_NOTICES.md +53 -0
- package/bin/pk.mjs +6 -0
- package/bin/postinstall.mjs +5 -0
- package/bin/strip-types.mjs +28 -0
- package/package.json +68 -4
- package/patches/README.md +111 -0
- package/patches/pi-ai-0.85.0.ts +122 -0
- package/patches/pi-ai-0.87.1.ts +122 -0
- package/scripts/apply-pi-streaming-patch.ts +86 -0
- package/skills/clerk/coverage/SKILL.md +35 -0
- package/skills/clerk/cross-check/SKILL.md +118 -0
- package/skills/clerk/deepen-dispatch/SKILL.md +69 -0
- package/skills/clerk/lane-follow-up/SKILL.md +94 -0
- package/skills/clerk/lane-plan/SKILL.md +107 -0
- package/skills/clerk/lane-slot/SKILL.md +113 -0
- package/skills/clerk/lane-topic/SKILL.md +116 -0
- package/skills/clerk/orientation/SKILL.md +160 -0
- package/skills/clerk/reconcile/SKILL.md +77 -0
- package/skills/clerk/skeleton-dispatch/SKILL.md +43 -0
- package/skills/clerk/spot-check/SKILL.md +52 -0
- package/skills/clerk/synthesis/SKILL.md +65 -0
- package/skills/table-follow-up/SKILL.md +42 -0
- package/src/cli.ts +319 -0
- package/src/codemap/README.md +13 -0
- package/src/codemap/engines.ts +102 -0
- package/src/codemap/facts.ts +504 -0
- package/src/context/agent-entry.ts +232 -0
- package/src/context/ask.ts +97 -0
- package/src/context/assemble.ts +1058 -0
- package/src/context/code-source.ts +103 -0
- package/src/context/k-briefs.ts +114 -0
- package/src/context/lineage.ts +100 -0
- package/src/context/object-brief.ts +300 -0
- package/src/context/owner-words.ts +191 -0
- package/src/context/pack-fixture.ts +342 -0
- package/src/context/project-rules.ts +160 -0
- package/src/intake/intake.ts +392 -0
- package/src/intake/material-rules.ts +166 -0
- package/src/intake/removal.ts +316 -0
- package/src/intake/skipped.ts +72 -0
- package/src/keeper/adjustment.ts +565 -0
- package/src/keeper/bounds/boundary.ts +509 -0
- package/src/keeper/bounds/command.ts +1665 -0
- package/src/keeper/bounds/paths.ts +164 -0
- package/src/keeper/bounds/reads.ts +718 -0
- package/src/keeper/bounds/shell-write-guard.ts +521 -0
- package/src/keeper/capabilities.ts +136 -0
- package/src/keeper/clerk-steps.ts +151 -0
- package/src/keeper/clerk-tools.ts +1424 -0
- package/src/keeper/conversation.ts +253 -0
- package/src/keeper/evidence.ts +662 -0
- package/src/keeper/fake-provider.ts +132 -0
- package/src/keeper/held.ts +15 -0
- package/src/keeper/history-tools.ts +163 -0
- package/src/keeper/jobs/answering.ts +53 -0
- package/src/keeper/key-check.ts +105 -0
- package/src/keeper/ledger-tools.ts +295 -0
- package/src/keeper/link-check.ts +185 -0
- package/src/keeper/merge.ts +260 -0
- package/src/keeper/note-origin.ts +56 -0
- package/src/keeper/numbers-check.ts +102 -0
- package/src/keeper/open-in-pi.ts +57 -0
- package/src/keeper/organize/absence.ts +266 -0
- package/src/keeper/organize/carried-on.ts +214 -0
- package/src/keeper/organize/clerk-coverage.ts +293 -0
- package/src/keeper/organize/clerk-prompts.ts +132 -0
- package/src/keeper/organize/clerk.ts +1395 -0
- package/src/keeper/organize/coverage-gate.ts +19 -0
- package/src/keeper/organize/coverage-tools.ts +765 -0
- package/src/keeper/organize/entries.ts +153 -0
- package/src/keeper/organize/entry-gate.ts +289 -0
- package/src/keeper/organize/fill-tools.ts +718 -0
- package/src/keeper/organize/follow-up.ts +280 -0
- package/src/keeper/organize/generation-check.ts +504 -0
- package/src/keeper/organize/graph.ts +230 -0
- package/src/keeper/organize/lane-tools.ts +289 -0
- package/src/keeper/organize/looked-tools.ts +54 -0
- package/src/keeper/organize/materials.ts +243 -0
- package/src/keeper/organize/owner-lines.ts +493 -0
- package/src/keeper/organize/place-tools.ts +216 -0
- package/src/keeper/organize/placement-inference.ts +1045 -0
- package/src/keeper/organize/placement-time.ts +320 -0
- package/src/keeper/organize/placing.ts +821 -0
- package/src/keeper/organize/positioning.ts +33 -0
- package/src/keeper/organize/readiness.ts +47 -0
- package/src/keeper/organize/reading.ts +401 -0
- package/src/keeper/organize/round-blocks.ts +65 -0
- package/src/keeper/organize/round-news.ts +164 -0
- package/src/keeper/organize/round-open.ts +586 -0
- package/src/keeper/organize/schedule-state.ts +39 -0
- package/src/keeper/organize/schedule.ts +137 -0
- package/src/keeper/organize/service.ts +409 -0
- package/src/keeper/organize/skills.ts +116 -0
- package/src/keeper/organize/slots.ts +85 -0
- package/src/keeper/organize/stage-gate.ts +175 -0
- package/src/keeper/organize/stage-tools.ts +471 -0
- package/src/keeper/organize/standing-notes.ts +141 -0
- package/src/keeper/organize/table-follow-up.ts +330 -0
- package/src/keeper/organize/takeover-clerk.ts +832 -0
- package/src/keeper/organize/takeover-state.ts +103 -0
- package/src/keeper/organize/takeover.ts +216 -0
- package/src/keeper/organize/update-pending.ts +535 -0
- package/src/keeper/organize/workbench-placement.ts +92 -0
- package/src/keeper/own-keys.ts +114 -0
- package/src/keeper/owner-text.ts +165 -0
- package/src/keeper/project-folder.ts +334 -0
- package/src/keeper/prompts.ts +101 -0
- package/src/keeper/references.ts +177 -0
- package/src/keeper/relook/compose.ts +120 -0
- package/src/keeper/relook/prompts.ts +36 -0
- package/src/keeper/roles.ts +259 -0
- package/src/keeper/round-tools.ts +227 -0
- package/src/keeper/route.ts +148 -0
- package/src/keeper/rules.ts +97 -0
- package/src/keeper/runtime.ts +1858 -0
- package/src/keeper/scope-tools.ts +194 -0
- package/src/keeper/sendback-text.ts +83 -0
- package/src/keeper/step-timing.ts +207 -0
- package/src/keeper/testing/clerk-script.ts +212 -0
- package/src/keeper/testing/streaming-arguments.ts +57 -0
- package/src/keeper/tools.ts +2340 -0
- package/src/keeper/utterance-tools.ts +64 -0
- package/src/keeper/verbatim.ts +397 -0
- package/src/ledger/adapters.ts +186 -0
- package/src/ledger/arrangements.ts +257 -0
- package/src/ledger/blame.ts +58 -0
- package/src/ledger/code-engine-child.ts +69 -0
- package/src/ledger/code-engine.ts +548 -0
- package/src/ledger/code.ts +607 -0
- package/src/ledger/docs.ts +294 -0
- package/src/ledger/git-read.ts +321 -0
- package/src/ledger/index.ts +1850 -0
- package/src/ledger/item-range.ts +16 -0
- package/src/ledger/lines.ts +328 -0
- package/src/ledger/numbering.ts +269 -0
- package/src/ledger/ranges.ts +74 -0
- package/src/ledger/rebuild-worker.ts +15 -0
- package/src/ledger/rebuild.ts +387 -0
- package/src/ledger/repo-scan.ts +181 -0
- package/src/ledger/schema.ts +450 -0
- package/src/ledger/sessions.ts +134 -0
- package/src/ledger/text-scan.ts +466 -0
- package/src/ledger/time.ts +185 -0
- package/src/ledger/ts-symbols.ts +180 -0
- package/src/ledger/views.ts +377 -0
- package/src/mcp/server.ts +127 -0
- package/src/mcp/tools.ts +187 -0
- package/src/model/ids.ts +23 -0
- package/src/model/k-types.ts +912 -0
- package/src/model/time.ts +85 -0
- package/src/model/types.ts +1382 -0
- package/src/model/views-k.ts +707 -0
- package/src/model/vocab.ts +269 -0
- package/src/process/analysis.ts +79 -0
- package/src/process/breakpoint-candidates.ts +383 -0
- package/src/process/breakpoints.ts +746 -0
- package/src/process/claim-check.ts +295 -0
- package/src/process/delivery-links.ts +52 -0
- package/src/process/facts.ts +487 -0
- package/src/process/generations.ts +138 -0
- package/src/process/index.ts +243 -0
- package/src/process/integration.ts +128 -0
- package/src/process/placement.ts +107 -0
- package/src/process/plan.ts +276 -0
- package/src/process/sendbacks.ts +244 -0
- package/src/process/text.ts +185 -0
- package/src/process/units.ts +543 -0
- package/src/process/unnumbered.ts +431 -0
- package/src/process/work.ts +884 -0
- package/src/scope/candidates.ts +86 -0
- package/src/scope/decisions.ts +229 -0
- package/src/scope/discover.ts +741 -0
- package/src/scope/ignore.ts +126 -0
- package/src/scope/ignored-place.ts +93 -0
- package/src/scope/skip.ts +161 -0
- package/src/scope/toolchain.ts +180 -0
- package/src/scope/worktree.ts +187 -0
- package/src/server/api.ts +549 -0
- package/src/server/app.ts +595 -0
- package/src/server/drafts-api.ts +170 -0
- package/src/server/folders-api.ts +243 -0
- package/src/server/graph-view.ts +639 -0
- package/src/server/http.ts +224 -0
- package/src/server/k-agent-api.ts +56 -0
- package/src/server/k-rounds-d99.ts +91 -0
- package/src/server/k-views.ts +405 -0
- package/src/server/keeper-page.ts +446 -0
- package/src/server/keys-api.ts +239 -0
- package/src/server/project-folder-api.ts +104 -0
- package/src/server/vendor.ts +14 -0
- package/src/server/workbench-content.ts +561 -0
- package/src/sources/anchor.ts +129 -0
- package/src/sources/files.ts +267 -0
- package/src/sources/gitobs.ts +41 -0
- package/src/sources/history.ts +231 -0
- package/src/sources/scan-worker.ts +10 -0
- package/src/sources/sessions/locate.ts +292 -0
- package/src/sources/sessions/read.ts +346 -0
- package/src/sources/sessions/scope.ts +42 -0
- package/src/sources/sessions/utterances.ts +223 -0
- package/src/sources/watch.ts +215 -0
- package/src/store/json-file.ts +50 -0
- package/src/store/paths.ts +39 -0
- package/src/store/project-store.ts +295 -0
- package/src/store/versions.ts +172 -0
- package/src/store/workspace.ts +216 -0
- package/src/table/check.ts +333 -0
- package/src/table/follow-up-tools.ts +30 -0
- package/src/table/follow-up.ts +344 -0
- package/src/table/import.ts +610 -0
- package/src/table/project-home.ts +61 -0
- package/src/table/sessions.ts +168 -0
- package/src/table/takeover.ts +483 -0
- package/src/util/git-check.ts +29 -0
- package/src/util/git.ts +411 -0
- package/src/util/own-ts.ts +15 -0
- package/src/util/paths.ts +225 -0
- package/template/FILLING-IN.md +148 -0
- package/template/examples/papertrail.json +747 -0
- package/template/projectkeeper.schema.json +1001 -0
- package/ui/app.js +1135 -0
- package/ui/failures.js +82 -0
- package/ui/flyout.js +120 -0
- package/ui/folder-chooser.js +264 -0
- package/ui/glossary.js +156 -0
- package/ui/graph-fit.js +157 -0
- package/ui/graph-material.js +61 -0
- package/ui/graph-palette.js +109 -0
- package/ui/graph-tools.js +62 -0
- package/ui/graph.js +1712 -0
- package/ui/index.html +31 -0
- package/ui/k/code-view.js +230 -0
- package/ui/k/draft-view.js +93 -0
- package/ui/k/k-views.css +224 -0
- package/ui/k/round-tree.js +450 -0
- package/ui/k/scope-k.js +159 -0
- package/ui/k-data.js +118 -0
- package/ui/k-fold.js +106 -0
- package/ui/k-process.css +302 -0
- package/ui/k-process.js +934 -0
- package/ui/keeper-activity.js +175 -0
- package/ui/keeper-view.js +801 -0
- package/ui/keeper.css +101 -0
- package/ui/key-order.js +62 -0
- package/ui/list-fold.js +67 -0
- package/ui/markdown.d.ts +21 -0
- package/ui/markdown.js +472 -0
- package/ui/placement.d.ts +92 -0
- package/ui/placement.js +445 -0
- package/ui/popover-place.d.ts +16 -0
- package/ui/popover-place.js +89 -0
- package/ui/popover.js +198 -0
- package/ui/project-folder.js +37 -0
- package/ui/shell-state.js +40 -0
- package/ui/strip-rows.js +171 -0
- package/ui/styles.css +492 -0
- package/ui/theme-pick.js +36 -0
- package/ui/theme.js +74 -0
- package/ui/themes/LICENSE.md +13 -0
- package/ui/themes/README.md +44 -0
- package/ui/themes/TOKENS.md +291 -0
- package/ui/themes/a1/board.css +1557 -0
- package/ui/themes/a1/pk.css +392 -0
- package/ui/themes/b1/board.css +1163 -0
- package/ui/themes/b1/pk.css +218 -0
- package/ui/themes/d2/board.css +518 -0
- package/ui/themes/d2/pk.css +243 -0
- package/ui/themes/guard.css +73 -0
- package/ui/themes/i1/board.css +776 -0
- package/ui/themes/i1/pk.css +223 -0
- package/ui/themes/manifest.json +13 -0
- package/ui/views.js +2796 -0
package/ui/keeper.css
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* The Keeper view's two pages and Keeper activity (Spec §6.10, §6.9; D105). Own layer only (§6.16): reads the theme's
|
|
2
|
+
--pk-* tokens with factory fallbacks and adds no colour of its own. */
|
|
3
|
+
|
|
4
|
+
/* The two pages */
|
|
5
|
+
.kp-tabs{align-self:flex-start;width:max-content}
|
|
6
|
+
.kp-tabs button{padding:4px 16px;font-weight:600}
|
|
7
|
+
#kp-page,#kp-settings{display:flex;flex-direction:column;gap:12px}
|
|
8
|
+
#kp-page>.stack{gap:12px}
|
|
9
|
+
.kp-head .sub .tag{margin-right:4px}
|
|
10
|
+
|
|
11
|
+
/* A block folded by default: one line that says what is inside and how much */
|
|
12
|
+
.kp-fold>summary{flex-wrap:wrap}
|
|
13
|
+
.kp-fold-head{color:var(--pk-text,#e7e7df)}
|
|
14
|
+
.kp-fold-note{color:var(--pk-accent,#e3b76f);font-size:11.5px}
|
|
15
|
+
.kp-fold>.content{padding-top:2px}
|
|
16
|
+
.section>.content>.kp-fold{margin-top:2px}
|
|
17
|
+
|
|
18
|
+
/* Takeover: the three depths */
|
|
19
|
+
.kp-depths{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
|
|
20
|
+
.kp-depth{display:flex;flex-direction:column;gap:5px;padding:9px 10px;border:1px solid var(--pk-control-border,var(--pk-line,#35352f));border-radius:var(--pk-radius,6px);cursor:pointer;font-size:12px;line-height:1.45}
|
|
21
|
+
.kp-depth .row{gap:7px}
|
|
22
|
+
.kp-depth input{margin:0}
|
|
23
|
+
.kp-depth.selected{border-color:var(--pk-accent,#e3b76f);background:var(--pk-accent-bg,#322c20)}
|
|
24
|
+
.kp-depth.disabled{cursor:default;opacity:.62}
|
|
25
|
+
.kp-depth:not(.disabled):hover{border-color:var(--pk-accent,#e3b76f)}
|
|
26
|
+
.kp-why{font-style:italic}
|
|
27
|
+
.kp-folder-row{align-items:flex-start;gap:12px;flex-wrap:nowrap}
|
|
28
|
+
.kp-folder-text{gap:4px;max-width:110ch;line-height:1.5}
|
|
29
|
+
.kp-folder-grant label.chk{display:flex;align-items:flex-start;gap:8px;line-height:1.5}
|
|
30
|
+
.kp-folder-grant label.chk input{margin:3px 0 0}
|
|
31
|
+
#kp-folder-name{max-width:320px;font-family:var(--pk-font-mono,ui-monospace,monospace)}
|
|
32
|
+
.kp-start-row{align-items:center}
|
|
33
|
+
@media (max-width:900px){.kp-depths{grid-template-columns:1fr}}
|
|
34
|
+
|
|
35
|
+
/* Takeover: progress as the Keeper works */
|
|
36
|
+
.kp-progress{border-top:1px solid var(--pk-line-soft,#2b2b26);padding-top:10px;gap:8px}
|
|
37
|
+
.kp-steps{margin:0;padding-left:20px;display:flex;flex-direction:column;gap:3px}
|
|
38
|
+
.kp-steps li>.btn{margin-left:6px}
|
|
39
|
+
.kp-list{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:4px}
|
|
40
|
+
.kp-lane-q{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100ch}
|
|
41
|
+
.kp-table-frame{overflow-x:auto}
|
|
42
|
+
|
|
43
|
+
/* The briefing */
|
|
44
|
+
.kp-briefing h4{margin-top:6px}
|
|
45
|
+
.kp-briefing h4:first-child{margin-top:0}
|
|
46
|
+
.kp-brief-text{line-height:1.6;max-width:110ch}
|
|
47
|
+
.kp-work td:first-child .tag{margin-left:6px}
|
|
48
|
+
.kp-work td,.kp-rounds td{padding:4px 8px}
|
|
49
|
+
|
|
50
|
+
/* Clear */
|
|
51
|
+
.kp-clear table td{padding:3px 8px;vertical-align:top}
|
|
52
|
+
.kp-clear .kp-num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
53
|
+
.kp-warn{color:var(--pk-accent,#e3b76f)}
|
|
54
|
+
#kp-clear-word{max-width:220px;font-family:var(--pk-font-mono,ui-monospace,monospace);letter-spacing:.06em}
|
|
55
|
+
|
|
56
|
+
/* Daily */
|
|
57
|
+
.kp-schedule-row{align-items:flex-end;gap:12px}
|
|
58
|
+
.kp-schedule-row .field{min-width:150px}
|
|
59
|
+
.kp-schedule-row select.input,.kp-schedule-row input.input{width:auto;min-width:150px}
|
|
60
|
+
.kp-days{gap:6px;min-height:34px}
|
|
61
|
+
.kp-day{display:inline-flex;align-items:center;gap:4px;font-size:12px;padding:3px 7px;border:1px solid var(--pk-control-border,var(--pk-line,#35352f));border-radius:var(--pk-radius-sm,5px);cursor:pointer}
|
|
62
|
+
.kp-day input{margin:0}
|
|
63
|
+
.kp-status{display:flex;flex-direction:column;gap:5px;border-top:1px solid var(--pk-line-soft,#2b2b26);padding-top:8px}
|
|
64
|
+
.kp-disabled{opacity:.6}
|
|
65
|
+
[hidden].field{display:none}
|
|
66
|
+
|
|
67
|
+
/* Keeper activity: a round is a row; its tree opens on a click */
|
|
68
|
+
.ka-head{margin:6px 0 0}
|
|
69
|
+
.ka-rounds{display:flex;flex-direction:column;gap:6px}
|
|
70
|
+
.ka-round{border:1px solid var(--pk-section-border,var(--pk-line-soft,#2b2b26));border-radius:var(--pk-radius,6px)}
|
|
71
|
+
.ka-round-row{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;padding:7px 10px;cursor:pointer;list-style:none;font-size:12px}
|
|
72
|
+
.ka-round-row::-webkit-details-marker{display:none}
|
|
73
|
+
.ka-round-row::before{content:'▸';font-size:10px;color:var(--pk-text-faint,#85857b)}
|
|
74
|
+
.ka-round[open]>.ka-round-row::before{content:'▾'}
|
|
75
|
+
.ka-round-row .btn{margin-left:auto}
|
|
76
|
+
.ka-round-result{flex-basis:100%;padding-left:16px}
|
|
77
|
+
.ka-round-tree .kv-rounds{padding:4px 8px 10px}
|
|
78
|
+
|
|
79
|
+
/* Model provider: the keys, the route, the backups (§6.10 key、模型与路由) */
|
|
80
|
+
.kp-keys td{vertical-align:top;padding:5px 8px}
|
|
81
|
+
.kp-keys .kp-mask{letter-spacing:.08em;white-space:nowrap}
|
|
82
|
+
.kp-key-state{min-width:180px;max-width:340px}
|
|
83
|
+
.kp-key-state .faint{font-size:11.5px;overflow-wrap:anywhere}
|
|
84
|
+
.kp-key-actions{white-space:nowrap}
|
|
85
|
+
.kp-key-actions .btn+.btn{margin-left:4px}
|
|
86
|
+
input.kp-key-lanes{width:58px}
|
|
87
|
+
.kp-num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
88
|
+
.kp-add-key,.kp-main-row{align-items:flex-end;gap:10px}
|
|
89
|
+
.kp-add-key .field,.kp-main-row .field{min-width:150px}
|
|
90
|
+
.kp-route{border-top:1px solid var(--pk-line-soft,#2b2b26);padding-top:10px;gap:8px}
|
|
91
|
+
.kp-route h5{margin:6px 0 4px;font-size:12px}
|
|
92
|
+
.kp-steps-route td{padding:4px 8px;vertical-align:top}
|
|
93
|
+
.kp-steps-route select{max-width:420px}
|
|
94
|
+
.kp-backup-list{margin:0;padding-left:20px;display:flex;flex-direction:column;gap:4px}
|
|
95
|
+
.kp-backup-row{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
|
|
96
|
+
.kp-backup-row select.input{width:auto;min-width:180px;max-width:360px}
|
|
97
|
+
.kp-backup-row select.kp-order-key{width:200px}
|
|
98
|
+
.kp-backup-row select.kp-order-model{width:300px}
|
|
99
|
+
#kp-backup-add{width:auto;min-width:240px;max-width:360px}
|
|
100
|
+
.kp-round-usage>summary{flex-wrap:wrap}
|
|
101
|
+
.kp-model-usage td{padding:4px 8px;vertical-align:top}
|
package/ui/key-order.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The key order on `Keeper` → `Model provider` (Spec §6.10 备用与并行; CKC-03 AC-35): "When a key runs out of quota or is
|
|
3
|
+
* rate-limited". Its first row is the main key — the same setting as `Main model — key` — and every row, the first too,
|
|
4
|
+
* chooses its key and its model, and can be moved or taken out; whichever row is first is the main key. Pure functions, so
|
|
5
|
+
* the order the page sends is tested without a browser.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** The rows of the order: the main model first, then the backups. */
|
|
9
|
+
export function orderRows(route) {
|
|
10
|
+
const thinking = route.main?.thinking ?? null;
|
|
11
|
+
return [...(route.main ? [{ provider: route.main.provider, id: route.main.id, thinking, keyName: route.main.keyName, usable: route.main.usable }] : []), ...route.backups];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* What the page posts for an order (`POST /api/projects/:id/route`): the first row is the main model, the rest the
|
|
16
|
+
* backups. The main model's thinking stays with the first place; a key moved down keeps the thinking it had as a backup,
|
|
17
|
+
* or the main one's. `null` for an empty order: one key at least stays.
|
|
18
|
+
*/
|
|
19
|
+
export function orderBody(next, thinking) {
|
|
20
|
+
if (!next.length) return null;
|
|
21
|
+
const [main, ...backups] = next;
|
|
22
|
+
return { model: { provider: main.provider, id: main.id, thinking }, backups: backups.map((b) => ({ provider: b.provider, id: b.id, thinking: b.thinking ?? thinking })) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The row at `i` moved by `d` (−1 earlier, +1 later). */
|
|
26
|
+
export function moveRow(list, i, d) {
|
|
27
|
+
const next = [...list];
|
|
28
|
+
const [x] = next.splice(i, 1);
|
|
29
|
+
next.splice(i + d, 0, x);
|
|
30
|
+
return next;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The model a key runs in a row: the one asked for when the key carries it, else the main model, else its first. */
|
|
34
|
+
export function modelFor(key, prefer, mainId) {
|
|
35
|
+
const has = (id) => Boolean(id) && key.models.some((m) => m.id === id);
|
|
36
|
+
return has(prefer) ? prefer : has(mainId) ? mainId : key.models[0]?.id;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Another key chosen for the row at `i` — every row chooses its key, the first too (one key per row). A key that sits in
|
|
41
|
+
* another row changes places with this one, each keeping its model; chosen for a lower row from the first, or for the
|
|
42
|
+
* first from a lower row, that is a change of the main key. A key outside the order takes the row, on the row's model
|
|
43
|
+
* when it carries it, else the main model, else its first; the key that was there leaves the order.
|
|
44
|
+
*/
|
|
45
|
+
export function chooseKey(list, i, key, mainId) {
|
|
46
|
+
const j = list.findIndex((x) => x.provider === key.id);
|
|
47
|
+
if (j === i || !list[i]) return list;
|
|
48
|
+
const next = [...list];
|
|
49
|
+
if (j >= 0) [next[i], next[j]] = [next[j], next[i]];
|
|
50
|
+
else next[i] = { provider: key.id, id: modelFor(key, list[i].id, mainId), thinking: list[i].thinking ?? null };
|
|
51
|
+
return next;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The main row's `Use` with a key that sits in a lower row of the order (DA): the two rows change places, as the list's
|
|
56
|
+
* own select does, and the first row runs the model chosen — the old main key stays in the order, where it was dropped
|
|
57
|
+
* before. `null` when the key is the main key already or is outside the order: the main model is then set as before.
|
|
58
|
+
*/
|
|
59
|
+
export function mainChosen(list, key, model, mainId) {
|
|
60
|
+
if (!key || list.findIndex((x) => x.provider === key.id) <= 0) return null;
|
|
61
|
+
return chooseKey(list, 0, key, mainId).map((x, i) => (i === 0 ? { ...x, id: model } : x));
|
|
62
|
+
}
|
package/ui/list-fold.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// The List's blocks that open and close (Spec §6.3 `List`; E153): the top plate, each earlier generation's row, each
|
|
2
|
+
// module's block, the foundation's and the cross-cutting ring's. Every one is folded to its head until the owner opens
|
|
3
|
+
// it (owner, 2026-10-01: 「这个我改过ui的地方,可以点击放开收上去,这样就是想看哪个module就可以点着看了,否则太长了」), so
|
|
4
|
+
// the whole List is about one screen and reads as the project's contents.
|
|
5
|
+
//
|
|
6
|
+
// What the owner opened is a choice of this browser, like the rail and the theme (shell-state.js): it lives in
|
|
7
|
+
// `localStorage`, per project, never in the project's assets, and holds across a refresh of the view's data and a
|
|
8
|
+
// reload of the page. Storage can be missing or throw (a private window, blocked site data); the choice then holds on
|
|
9
|
+
// this page only. A work row's own fold — its `Process` · `Outcome` — is the process view's one rule (k-fold.js).
|
|
10
|
+
//
|
|
11
|
+
// No DOM here, so the rule is tested with `node --test` (src/ui/list-fold.test.ts). views.js draws the blocks from it.
|
|
12
|
+
|
|
13
|
+
export const LIST_FOLD_KEY = 'pk.list.open';
|
|
14
|
+
|
|
15
|
+
/** The keys of the List's blocks. A module's and the foundation's block go by their area; the ring's has no area. */
|
|
16
|
+
export const PLATE_KEY = 'plate';
|
|
17
|
+
export const CROSS_KEY = 'block:cross';
|
|
18
|
+
export const blockKey = (areaId) => `block:${areaId}`;
|
|
19
|
+
export const generationKey = (generationId) => `gen:${generationId}`;
|
|
20
|
+
|
|
21
|
+
/** `storage`: the browser's `localStorage` when left out; null for none (the choice then holds on the page). */
|
|
22
|
+
export function createListFold(storage) {
|
|
23
|
+
const store = () => (storage === undefined ? globalThis.localStorage : storage); // read inside try: it can throw
|
|
24
|
+
let here = null; // project id → the keys open in it; read from the browser once, kept on the page when there is none
|
|
25
|
+
const read = () => {
|
|
26
|
+
if (here) return here;
|
|
27
|
+
here = new Map();
|
|
28
|
+
try {
|
|
29
|
+
const kept = JSON.parse(store()?.getItem(LIST_FOLD_KEY) ?? '{}');
|
|
30
|
+
if (kept && typeof kept === 'object' && !Array.isArray(kept)) {
|
|
31
|
+
for (const [pid, keys] of Object.entries(kept)) if (Array.isArray(keys)) here.set(pid, new Set(keys.filter((k) => typeof k === 'string')));
|
|
32
|
+
}
|
|
33
|
+
} catch { /* nothing kept, or not ours: every block folded */ }
|
|
34
|
+
return here;
|
|
35
|
+
};
|
|
36
|
+
const write = () => {
|
|
37
|
+
const kept = {};
|
|
38
|
+
for (const [pid, keys] of read()) if (keys.size) kept[pid] = [...keys];
|
|
39
|
+
try { store()?.setItem(LIST_FOLD_KEY, JSON.stringify(kept)); } catch { /* remembered on this page only */ }
|
|
40
|
+
};
|
|
41
|
+
const openIn = (pid) => read().get(pid) ?? new Set();
|
|
42
|
+
const set = (pid, key, open) => {
|
|
43
|
+
const keys = read().get(pid) ?? new Set();
|
|
44
|
+
open = Boolean(open);
|
|
45
|
+
if (open === keys.has(key)) return open;
|
|
46
|
+
if (open) keys.add(key); else keys.delete(key);
|
|
47
|
+
read().set(pid, keys);
|
|
48
|
+
write();
|
|
49
|
+
return open;
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
/** Whether a block is open: only when the owner opened it. */
|
|
53
|
+
isOpen: (pid, key) => openIn(pid).has(key),
|
|
54
|
+
set,
|
|
55
|
+
toggle: (pid, key) => set(pid, key, !openIn(pid).has(key)),
|
|
56
|
+
/**
|
|
57
|
+
* `Expand all` opens every block the List draws now (`keys`); `Fold all` folds every block of the project, the
|
|
58
|
+
* ones no longer drawn with them, so nothing stale is kept.
|
|
59
|
+
*/
|
|
60
|
+
setAll(pid, keys, open) {
|
|
61
|
+
read().set(pid, open ? new Set([...openIn(pid), ...keys]) : new Set());
|
|
62
|
+
write();
|
|
63
|
+
},
|
|
64
|
+
allOpen: (pid, keys) => keys.length > 0 && keys.every((k) => openIn(pid).has(k)),
|
|
65
|
+
anyOpen: (pid, keys) => keys.some((k) => openIn(pid).has(k)),
|
|
66
|
+
};
|
|
67
|
+
}
|
package/ui/markdown.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Types for markdown.js, so the node tests under src/ check against them. The browser never loads this file. */
|
|
2
|
+
export type Inline =
|
|
3
|
+
| { t: 'text'; v: string }
|
|
4
|
+
| { t: 'br' }
|
|
5
|
+
| { t: 'code'; v: string }
|
|
6
|
+
| { t: 'b' | 'i' | 's'; kids: Inline[] }
|
|
7
|
+
/** `href` is null when the address is not plain http(s): the words are shown, and they go nowhere. */
|
|
8
|
+
| { t: 'a'; href: string | null; raw: string; img?: boolean; kids: Inline[] }
|
|
9
|
+
| { t: 'cite'; id: string };
|
|
10
|
+
export type Block =
|
|
11
|
+
| { t: 'p'; kids: Inline[] }
|
|
12
|
+
| { t: 'h'; level: number; kids: Inline[] }
|
|
13
|
+
| { t: 'code'; lang: string; text: string }
|
|
14
|
+
| { t: 'hr' }
|
|
15
|
+
| { t: 'quote'; kids: Block[] }
|
|
16
|
+
| { t: 'list'; ordered: boolean; start: number | null; items: Block[][] }
|
|
17
|
+
| { t: 'table'; align: string[]; head: Inline[][]; rows: Inline[][][] };
|
|
18
|
+
export function parseMarkdown(text: string): Block[];
|
|
19
|
+
export function parseInline(src: string): Inline[];
|
|
20
|
+
export function safeHref(href: string): string | null;
|
|
21
|
+
export function fillMarkdown<T>(target: T, text: string, options?: { onCite?: (id: string) => void }): T;
|