rvlib-shipkit 0.10.0 → 0.13.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 (61) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/README.md +17 -3
  3. package/package.json +5 -2
  4. package/src/asks/asks-queue.ts +42 -0
  5. package/src/assets/kit-config.ts +2 -3
  6. package/src/assets/roll-workspace.ts +2 -1
  7. package/src/assets/run-assets.ts +0 -11
  8. package/src/assets/server/studio-server.ts +7 -24
  9. package/src/cli/cmd_Assets.ts +3 -14
  10. package/src/cli/cmd_Check.ts +22 -4
  11. package/src/cli/cmd_Needs.ts +47 -0
  12. package/src/cli/cmd_Reflect.ts +116 -0
  13. package/src/cli/cmd_Rules.ts +32 -0
  14. package/src/cli/cmd_Serve.ts +1 -1
  15. package/src/cli.ts +7 -2
  16. package/src/collections/actions.ts +85 -0
  17. package/src/collections/collection-types.ts +65 -0
  18. package/src/collections/graphs.ts +34 -0
  19. package/src/collections/pings.ts +72 -0
  20. package/src/collections/posts.ts +125 -0
  21. package/src/collections/providers.ts +275 -0
  22. package/src/contracts/apply-contracts.ts +47 -0
  23. package/src/contracts/run-contracts.ts +8 -3
  24. package/src/core/canonical-scripts.ts +46 -0
  25. package/src/core/check-suite.ts +6 -3
  26. package/src/core/locked-write.ts +39 -0
  27. package/src/core/motherhome.ts +87 -0
  28. package/src/core/run-check.ts +28 -15
  29. package/src/core/run-ci.ts +4 -0
  30. package/src/core/write-status.ts +7 -26
  31. package/src/init-repo.ts +24 -11
  32. package/src/reflect/prompt-corpus.ts +305 -0
  33. package/src/repo-config.ts +26 -38
  34. package/src/rules/SK001-package-json-shape.ts +156 -0
  35. package/src/rules/SK002-contracts-fulfillness.ts +19 -0
  36. package/src/rules/SK003-claude-md-shape.ts +64 -0
  37. package/src/rules/SK004-gitignore-policy.ts +105 -0
  38. package/src/rules/SK005-rvlib-versions.ts +99 -0
  39. package/src/rules/SK006-npm-publish-safety.ts +42 -0
  40. package/src/rules/SK007-github-visibility.ts +63 -0
  41. package/src/rules/rule-registry.ts +32 -0
  42. package/src/rules/rule.ts +62 -0
  43. package/src/rules/run-fix.ts +57 -0
  44. package/src/serve/hub-app/col-card.tsx +192 -0
  45. package/src/serve/hub-app/cols-svc.ts +136 -0
  46. package/src/serve/hub-app/geometry-view.tsx +155 -0
  47. package/src/serve/hub-app/health-svc.ts +49 -0
  48. package/src/serve/hub-app/hub-app.tsx +240 -127
  49. package/src/serve/hub-app/hub-st.ts +169 -0
  50. package/src/serve/hub-app/hub-types.ts +72 -0
  51. package/src/serve/hub-app/hub.css +286 -35
  52. package/src/serve/hub-app/index.html +1 -1
  53. package/src/serve/hub-app/md.ts +24 -0
  54. package/src/serve/hub-app/media-svc.ts +429 -0
  55. package/src/serve/hub-app/media-view.tsx +299 -0
  56. package/src/serve/run-serve.ts +46 -91
  57. package/src/serve/serve-hub.ts +99 -49
  58. package/src/shipkit.ts +13 -6
  59. package/src/assets/server/studio-page.ts +0 -687
  60. package/src/core/package-json-check.ts +0 -88
  61. package/src/serve/hub-pages.ts +0 -45
@@ -1,41 +1,292 @@
1
- /* house palette — matches the studio/graph family. Plain CSS on purpose:
2
- a published lib must not impose bunfig plugins (tailwind) on consumers. */
1
+ /* house palette — matches the graph family. Plain CSS on purpose: a
2
+ published lib must not impose bunfig plugins (tailwind) on consumers. */
3
3
  :root{--bg:#141210;--panel:#1e1a15;--panel2:#262019;--line:#332b22;--ink:#f3ebe0;--dim:#a89a88;
4
- --accent:#e88c28;--gold:#ffcf5c;--teal:#58c0b0;--red:#e05252}
5
- *{box-sizing:border-box}
6
- body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.6 system-ui,sans-serif;min-height:100vh}
7
- .app{display:flex;min-height:100vh}
8
- .wrap{max-width:900px;margin:0 auto;padding:34px 26px;flex:1}
9
- h1{font-size:20px;margin:0}
10
- h1 small{color:var(--dim);font-weight:400;font-size:13px;margin-left:10px}
11
- .crumb{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--accent);margin-bottom:6px}
12
-
13
- .skrail{position:sticky;top:0;align-self:flex-start;height:100vh;width:46px;display:flex;flex-direction:column;
14
- align-items:center;padding-top:10px;gap:4px;background:#17140f;border-right:1px solid var(--line);flex:none}
15
- .skrail a{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:9px;
16
- font-size:17px;text-decoration:none;border-left:2px solid transparent;filter:grayscale(35%);opacity:.75;position:relative}
17
- .skrail a:hover{background:var(--panel2);filter:none;opacity:1}
18
- .skrail a.on{background:var(--panel2);border-left-color:var(--accent);filter:none;opacity:1}
19
- .skrail a i{position:absolute;top:4px;right:4px;width:8px;height:8px;border-radius:50%;background:var(--red)}
20
-
21
- .clear{margin-top:30px;font-size:17px;color:var(--teal)}
22
- section{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--gold);border-radius:12px;
23
- padding:14px 18px;margin-top:16px}
24
- section.red{border-left-color:var(--red)}
25
- h2{font-size:14px;margin:0 0 8px;display:flex;align-items:center;gap:8px}
26
- h2 a{color:var(--ink);text-decoration:none}h2 a:hover{color:var(--gold)}
27
- h2 em{font-style:normal;font-size:12px;color:var(--dim);background:var(--panel2);border-radius:8px;padding:1px 8px}
28
- .line{font:12.5px/1.7 ui-monospace,Menlo,monospace;color:var(--dim);white-space:pre-wrap}
29
- .line a{color:var(--dim);text-decoration:underline dotted}.line a:hover{color:var(--gold)}
30
- section.red .line{color:#e8a0a0}
31
- .sub{margin-top:10px;font-size:12px;color:var(--dim)}
32
- .sub a{color:var(--dim)}
33
-
34
- .bar{display:flex;align-items:center;gap:12px;margin:18px 0 12px}
35
- button{background:var(--accent);color:#1a1206;border:0;border-radius:9px;padding:8px 14px;font-weight:650;cursor:pointer}
4
+ --accent:#e88c28;--gold:#ffcf5c;--teal:#58c0b0;--red:#e05252;--blue:#6ca0e8;--chk1:#221d17;--chk2:#1a1611}
5
+ *{box-sizing:border-box}html,body,#root{height:100%}
6
+ body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 system-ui,sans-serif;overflow:hidden}
7
+ .app{display:grid;grid-template-columns:46px 290px 1fr;height:100vh}
8
+
9
+ /* ── icon rail: jump-to-section, never a navigation ── */
10
+ .skrail{width:46px;height:100vh;display:flex;flex-direction:column;align-items:center;padding-top:10px;gap:4px;
11
+ background:#17140f;border-right:1px solid var(--line)}
12
+ .skrail .ric{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:9px;
13
+ font-size:17px;cursor:pointer;filter:grayscale(35%);opacity:.75;position:relative;user-select:none}
14
+ .skrail .ric:hover{background:var(--panel2);filter:none;opacity:1}
15
+ .skrail .ric i{position:absolute;top:4px;right:4px;width:8px;height:8px;border-radius:50%;background:var(--red)}
16
+
17
+ /* ── sidebar ── */
18
+ aside{border-right:1px solid var(--line);background:var(--panel);overflow-y:auto;height:100vh}
19
+ .shead{position:sticky;top:0;background:var(--panel);padding:12px 14px;border-bottom:1px solid var(--line);
20
+ font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);z-index:2;display:flex;justify-content:space-between;align-items:center}
21
+ /* segmented severity filter — all · needs · red */
22
+ .seg{display:inline-flex;border:1px solid var(--line);border-radius:7px;overflow:hidden;flex:none}
23
+ .seg button{background:var(--bg);color:var(--dim);border:0;border-radius:0;padding:2px 8px;font-size:10px;font-weight:600;
24
+ letter-spacing:.06em;text-transform:uppercase;cursor:pointer}
25
+ .seg button+button{border-left:1px solid var(--line)}
26
+ .seg button:hover{color:var(--ink)}
27
+ .seg button.on{background:var(--accent);color:#1a1206}
28
+ .ssearch{padding:8px 12px}
29
+ .ssearch input{width:100%;background:var(--bg);border:1px solid var(--line);border-radius:8px;color:var(--ink);
30
+ padding:6px 10px;font-size:12.5px;outline:none}
31
+ .ssearch input:focus{border-color:var(--accent)}
32
+ /* section header — hairline top border, tight letterspacing; media cats
33
+ and collection sections share this treatment EXACTLY */
34
+ .gname{padding:9px 14px 4px;font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:var(--accent);
35
+ border-top:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;scroll-margin-top:46px}
36
+ .gname .cn{cursor:pointer}.gname .cn:hover{color:var(--gold)}
37
+ .gname .cnt{font-size:11px;color:var(--dim);letter-spacing:0;text-align:right;min-width:18px}
38
+ .warnline{color:var(--red);text-transform:none;letter-spacing:0}
39
+ .fold{cursor:pointer;color:var(--dim);font-size:15px;display:inline-block;width:22px;text-align:center;
40
+ padding:2px 3px;margin:-2px 0;border-radius:6px;user-select:none}
41
+ .fold:hover{color:var(--ink);background:var(--panel2)}
42
+ .catgen button{background:var(--panel2);color:var(--dim);border:1px solid var(--line);border-radius:6px;
43
+ padding:1px 6px;font-size:11px;font-weight:700;margin-left:3px;cursor:pointer}
44
+ .catgen button:hover{color:var(--ink);border-color:var(--accent)}
45
+ /* items sit one step in from their header, on a subtle left guide line;
46
+ the full row hovers, the selected row keeps the accent bar */
47
+ .arow{position:relative;display:flex;align-items:center;gap:8px;padding:3px 12px 3px 24px;cursor:pointer;
48
+ border-left:3px solid transparent}
49
+ .arow::before{content:'';position:absolute;left:15px;top:0;bottom:0;width:1px;background:var(--line)}
50
+ .arow:hover{background:var(--panel2)}
51
+ .arow.sel{background:rgba(232,140,40,.18);border-left-color:var(--accent)}
52
+ .arow.sel::before{background:transparent}
53
+ .arow .lb{flex:1;min-width:0}.arow .lb b{display:block;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
54
+ .arow .lb span{font-size:11px;color:var(--dim);display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
55
+ .dot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--line)}
56
+ .dot.done{background:var(--gold)}.dot.some{background:var(--accent)}
57
+ .dot.miss{background:#e5484d}.dot.stale{background:#f5a524}
58
+ .cdot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--line)}
59
+ .cdot.red{background:var(--red)}.cdot.yellow{background:var(--gold)}
60
+
61
+ /* ── main column ── */
62
+ main{display:flex;flex-direction:column;height:100vh;overflow:hidden}
63
+ /* topbar: the prompt is its OWN full-width row (small mono, auto-grows to
64
+ its content, scrolls only past ~14 lines), then ONE slim toolbar row */
65
+ .bar{display:flex;flex-direction:column;gap:8px;padding:10px 16px;border-bottom:1px solid var(--line);background:var(--panel)}
66
+ textarea{width:100%;min-height:64px;background:var(--bg);color:var(--ink);border:1px solid var(--line);
67
+ border-radius:9px;padding:9px 11px;font:12px/1.5 ui-monospace,Menlo,monospace;resize:vertical}
68
+ .bar .prompt{min-height:36px;max-height:252px;overflow-y:auto;resize:none;font:11px/1.5 ui-monospace,Menlo,monospace;padding:7px 10px}
69
+ .toolbar{display:flex;gap:8px;align-items:center;min-height:30px}
70
+ .toolbar button{padding:5px 11px;font-size:12px}
71
+ .toolbar .sp{flex:1}
72
+ .toolbar .xn{display:flex;gap:3px;align-items:center;font-size:12px;color:var(--dim)}
73
+ .toolbar .xn select{padding:4px 5px;font-size:12px}
74
+ button{background:var(--accent);color:#1a1206;border:0;border-radius:9px;padding:8px 13px;font-weight:650;cursor:pointer}
75
+ button.ghost{background:var(--panel2);color:var(--ink);border:1px solid var(--line)}
36
76
  button:disabled{opacity:.45;cursor:default}
77
+ select,input[type=number]{background:var(--bg);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:7px}
78
+ .status{font-size:12px;color:var(--dim);text-align:right;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:46vw;flex:none}
79
+ .spin{width:12px;height:12px;border:2px solid var(--line);border-top-color:var(--gold);border-radius:50%;animation:rot 1s linear infinite;flex:none}
80
+ @keyframes rot{to{transform:rotate(360deg)}}
81
+ .progbar{display:block;width:120px;height:4px;background:var(--panel2);border-radius:2px;overflow:hidden;flex:none}
82
+ .progbar i{display:block;height:100%;width:0;background:var(--gold);transition:width .3s}
83
+ .detnote{font-size:11px;color:var(--teal);line-height:1.25}
84
+ .jrow{display:flex;gap:8px;margin-top:6px;align-items:center}
85
+ .jmsg{font-size:12px;color:var(--dim)}
86
+ kbd{background:var(--panel2);border:1px solid var(--line);border-radius:4px;padding:0 5px;font-size:11px}
87
+ .help{font-size:11px;color:var(--dim);padding:4px 16px;border-top:1px solid var(--line)}
88
+ .errs{background:#3a1717;border-bottom:1px solid #5a2222;color:#f0c0c0;font:12px/1.5 ui-monospace,Menlo,monospace;
89
+ padding:6px 16px;white-space:pre-wrap}
90
+
91
+ /* ── per-asset info chips ── */
92
+ .info{flex-wrap:wrap;gap:6px;align-items:center;padding:6px 16px;border-bottom:1px solid var(--line);background:var(--panel2)}
93
+ .info .chip{display:inline-flex;align-items:center;gap:5px;background:var(--bg);border:1px solid var(--line);border-radius:6px;
94
+ padding:2px 8px;font-size:11px;color:var(--ink);max-width:360px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
95
+ .info .chip b{color:var(--accent);font-weight:650;letter-spacing:.05em;text-transform:uppercase;font-size:10px}
96
+ .info .chip.warn{border-color:var(--red);color:#f0c0c0}.info .chip.warn b{color:var(--red)}
97
+ .info .chip code{font:11px ui-monospace,Menlo,monospace;color:var(--dim)}
98
+
99
+ /* ── stage ── */
100
+ .stage{position:relative;flex:1;min-height:0;display:flex;align-items:center;justify-content:center;padding:18px;overflow:hidden;
101
+ background:conic-gradient(var(--chk1) 90deg,var(--chk2) 0 180deg,var(--chk1) 0 270deg,var(--chk2) 0) 0/34px 34px}
102
+ .stage img.roll{max-width:100%;max-height:100%;object-fit:contain;filter:drop-shadow(0 6px 20px #0008);z-index:5;position:relative}
103
+ .stage .ph,.stagewrap .ph{color:var(--dim)}
104
+ .stagewrap{flex:1;min-height:0;overflow-y:auto;display:flex;align-items:flex-start;justify-content:center;padding:18px}
105
+ .stageaud{display:flex;flex-direction:column;align-items:center;gap:14px;font-size:48px;padding:40px}
106
+ .stageaud audio{width:min(480px,80vw)}
107
+
108
+ /* ── geometry overlay ── */
109
+ #ov{position:absolute;border:2px solid var(--gold);border-radius:50%;background:rgba(255,207,92,.14);cursor:move;z-index:10}
110
+ #ov .hdl{position:absolute;right:-7px;top:-7px;width:14px;height:14px;background:var(--gold);border-radius:3px;cursor:nesw-resize}
111
+ #ov .arrow{position:absolute;left:0;right:0;top:50%;border-top:1px dashed var(--gold);opacity:.8}
112
+ #ov .sz{position:absolute;left:50%;top:50%;transform:translate(-50%,-120%);font-size:11px;color:var(--gold);
113
+ background:#000a;padding:0 5px;border-radius:4px;white-space:nowrap}
114
+ .plumb{position:absolute;width:0;border-left:1px dashed rgba(255,207,92,.5);top:-2000px;bottom:-2000px;z-index:9}
115
+ .leg{position:absolute;z-index:10;border:1px solid;border-radius:3px}
116
+ .leg.far{border-color:var(--blue);z-index:3}
117
+ .leg.near{border-color:var(--red);z-index:7}
118
+ .leg .hip{position:absolute;top:-3px;left:50%;transform:translateX(-50%);width:6px;height:6px;border-radius:50%;background:currentColor}
119
+ .leg .foot{position:absolute;bottom:-4px;left:50%;transform:translateX(-50%);width:12px;height:7px;border-radius:50%;background:var(--gold)}
120
+ .leg .lh{position:absolute;right:-6px;top:-6px;width:11px;height:11px;background:currentColor;border-radius:3px;cursor:nesw-resize}
121
+ .leg .tag{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font-size:9px;opacity:.85;white-space:nowrap}
122
+ .pill{position:absolute;border-radius:999px;z-index:3;opacity:.9}
123
+ .pill.near{z-index:7}
124
+ #rectbar{position:absolute;left:14px;bottom:14px;z-index:11;gap:8px;align-items:center;
125
+ background:#000b;border:1px solid var(--line);border-radius:10px;padding:7px 10px;font-size:12px}
126
+ #rectbar input[type=number]{width:64px;padding:4px}
127
+ #rectbar .ro{color:var(--dim);font:11px ui-monospace,Menlo,monospace}
128
+ #rectbar label{display:flex;gap:6px;align-items:center}
129
+
130
+ /* ── category grid + lineup ── */
131
+ #catgrid{flex:1;min-height:0;overflow-y:auto;padding:14px}
132
+ .cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px}
133
+ .card{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:8px;cursor:pointer}
134
+ .card:hover{border-color:var(--accent)}
135
+ .card .th{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;
136
+ background:conic-gradient(var(--chk1) 90deg,var(--chk2) 0 180deg,var(--chk1) 0 270deg,var(--chk2) 0) 0/14px 14px}
137
+ .card .th img{width:100%;height:100%;object-fit:contain}
138
+ .card .th .ovmini{position:absolute;border:1.5px solid var(--gold);border-radius:50%;background:rgba(255,207,92,.12)}
139
+ .card .th .spin{position:absolute;top:8px;right:8px}
140
+ .card .nm{font-size:12px;margin:6px 0 4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer}
141
+ .card .nm:hover{color:var(--gold)}
142
+ .card .row{display:flex;gap:6px;align-items:center;font-size:11px;color:var(--dim)}
143
+ .card .row input{width:58px;padding:3px}
144
+ .card .aud{aspect-ratio:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;font-size:28px}
145
+ .card .aud audio{width:92%;height:24px}
146
+ #allbar{display:flex;gap:8px;align-items:center;margin:0 0 12px;font-size:12px;color:var(--dim)}
147
+ #allbar label{display:flex;gap:6px;align-items:center}
148
+ #mixsec{margin-top:16px}
149
+ #mixbox{position:relative;height:420px;overflow-x:auto;overflow-y:hidden;background:var(--panel);
150
+ border:1px solid var(--line);border-radius:10px}
151
+ #mixinner{position:relative;height:100%}
152
+ #mixground{position:absolute;left:0;right:0;bottom:0;height:44px;background:#1d2b1a;border-top:1px solid #2c4527}
153
+ .mixsprite{position:absolute;image-rendering:auto;pointer-events:none}
154
+ .mixlabel{position:absolute;font-size:10px;color:var(--dim);text-align:center;transform:translateX(-50%)}
155
+ .ruler{position:absolute;left:0;right:0;bottom:44px;top:0;pointer-events:none;
156
+ background:
157
+ repeating-linear-gradient(to top,rgba(255,255,255,.28) 0 1px,transparent 1px 240px),
158
+ repeating-linear-gradient(to top,rgba(255,255,255,.16) 0 1px,transparent 1px 120px),
159
+ repeating-linear-gradient(to top,rgba(255,255,255,.07) 0 1px,transparent 1px 24px)}
160
+ .rlabel{position:absolute;left:4px;font-size:9px;color:var(--dim)}
161
+ .rlabel.b{font-weight:700;color:var(--ink)}
162
+
163
+ /* ── roll strip ── */
164
+ .strip{height:150px;flex:none;border-top:1px solid var(--line);background:var(--panel);display:flex;gap:10px;padding:12px;overflow-x:auto}
165
+ .vig{position:relative;height:100%;aspect-ratio:1;flex:none;border:2px solid var(--line);border-radius:10px;overflow:hidden;cursor:pointer;
166
+ background:conic-gradient(var(--chk1) 90deg,var(--chk2) 0 180deg,var(--chk1) 0 270deg,var(--chk2) 0) 0/12px 12px}
167
+ .vig img{width:100%;height:100%;object-fit:contain}
168
+ .vig.active{border-color:var(--ink)}
169
+ .vig.picked{border-color:var(--gold);box-shadow:0 0 0 2px var(--gold) inset}
170
+ .vig.alt{border-color:var(--teal)}
171
+ .vig .st{position:absolute;top:4px;right:6px;font-size:16px;filter:drop-shadow(0 1px 2px #000)}
172
+ .vig .btns{position:absolute;bottom:4px;left:4px;right:4px;display:flex;gap:4px;justify-content:center;opacity:0;transition:opacity .15s}
173
+ .vig:hover .btns{opacity:1}
174
+ .vig .btns button{padding:2px 7px;font-size:11px;border-radius:6px}
175
+ .vig .btns .b-alt{background:var(--teal);color:#06342c}
176
+ .vig .btns .b-del{background:var(--red);color:#fff}
177
+ .vig.aud{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;font-size:20px}
178
+ .vig.aud audio{width:100%;height:24px}
179
+ .vig.gen{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:var(--dim);font-size:11px}
180
+ .strip .ph{color:var(--dim);align-self:center;padding:0 12px}
181
+
182
+ /* ── drawer ── */
183
+ .drawer{flex:none;border-top:1px solid var(--line);background:var(--panel)}
184
+ .tabs{display:flex;gap:2px;padding:6px 12px 0}
185
+ .tabs button{background:var(--panel2);color:var(--dim);border:1px solid var(--line);border-bottom:0;border-radius:8px 8px 0 0;
186
+ padding:5px 12px;font-size:12px;font-weight:400}
187
+ .tabs button.on{color:var(--ink);background:var(--bg)}
188
+ .tabs .sp{flex:1}
189
+ .tabs .ghost{border:1px solid var(--line);border-radius:8px;font-size:12px;padding:5px 12px}
190
+ #drawerbody{padding:10px 12px}
191
+ #jsonta{min-height:140px}
192
+
193
+ /* ── asset remark row ── */
194
+ .assetrem{flex:none;border-top:1px solid var(--line);background:var(--panel);padding:8px 12px}
195
+ .assetrem .row{display:flex;gap:10px;align-items:center}
196
+ .assetrem input{flex:1;background:var(--bg);border:1px solid var(--line);border-radius:8px;color:var(--ink);padding:7px 10px;font-size:12.5px}
197
+ .assetrem .rem{color:var(--teal);border-left:2px solid var(--teal);padding-left:10px;font-size:12.5px;margin:0 0 8px;white-space:pre-wrap}
198
+
199
+ /* ── collection item card + status card ── */
200
+ .colcard{max-width:760px;width:100%;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:18px 22px;text-align:left}
201
+ .colcard h3{margin:0 0 10px;font-size:15px;display:flex;gap:10px;align-items:baseline}
202
+ .colcard .truth{margin-left:auto;font:11px ui-monospace,Menlo,monospace;color:var(--dim)}
203
+ .colcard .rem{color:var(--teal);border-left:2px solid var(--teal);padding-left:10px;font-size:12.5px;margin:8px 0;white-space:pre-wrap}
204
+ .colcard .row{display:flex;gap:10px;align-items:center;margin-top:10px;flex-wrap:wrap}
205
+ .colcard input{flex:1;min-width:220px;background:var(--bg);border:1px solid var(--line);border-radius:8px;color:var(--ink);padding:7px 10px;font-size:12.5px}
206
+ .colcard code{display:block;background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font:12px ui-monospace,Menlo,monospace;white-space:pre-wrap;max-height:200px;overflow-y:auto;margin-top:8px}
207
+ .colcard .cand{background:var(--bg);border:1px solid var(--line);border-radius:9px;padding:9px 12px;margin-top:8px}
208
+ .colcard .candhint{color:var(--dim);font-size:12.5px;white-space:pre-wrap}
209
+ .colcard .err,.assetrem .err{color:var(--red);margin-top:8px;font-size:12.5px}
210
+ .colcard a{color:var(--accent)}
211
+
212
+ .back{cursor:pointer;color:var(--dim);font-size:12px}
213
+ .back:hover{color:var(--ink)}
214
+ .colcard .back{margin-bottom:8px}
215
+
216
+ /* ── collection view (one card per instance) ── */
217
+ .colgrid{flex:1;min-height:0;overflow-y:auto;padding:14px 16px}
218
+ .cghead{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--accent);
219
+ display:flex;gap:10px;align-items:baseline;margin:0 0 12px}
220
+ .cghead .cnt{font-size:11px;color:var(--dim);letter-spacing:0;text-transform:none}
221
+ .cgcards{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px}
222
+ .cgcard{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:10px 12px;cursor:pointer;min-width:0}
223
+ .cgcard:hover{border-color:var(--accent)}
224
+ .cgcard .cgtop{display:flex;gap:8px;align-items:center;min-width:0}
225
+ .cgcard .cgtop b{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
226
+ .cgcard .truth{display:block;font:11px ui-monospace,Menlo,monospace;color:var(--dim);margin-top:4px;
227
+ white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
228
+ .cgcard .cgrem{color:var(--teal);border-left:2px solid var(--teal);padding-left:8px;font-size:12px;margin-top:6px;
229
+ white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
230
+
231
+ /* ── graph item view (the graph itself fills the stage) ── */
232
+ .graphview{flex:1;min-height:0;display:flex;flex-direction:column;gap:10px;padding:12px 16px 16px}
233
+ .gvhead{display:flex;gap:10px;align-items:center;font-size:13px;min-width:0}
234
+ .gvhead .gvtext{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
235
+ .graphview iframe{flex:1;min-height:0;width:100%;border:1px solid var(--line);border-radius:12px;background:var(--bg)}
236
+
237
+ /* ── md output (truth-file card bodies — marked + sanitize allowlist) ── */
238
+ .md{font-size:12px;line-height:1.5;color:var(--ink)}
239
+ .md table{border-collapse:collapse;margin:6px 0;font-size:11.5px;max-width:100%}
240
+ .md th,.md td{border:1px solid var(--line);padding:3px 8px;text-align:left;vertical-align:top}
241
+ .md th{color:var(--accent);font-weight:650;font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;background:var(--panel2)}
242
+ .md blockquote{margin:6px 0;padding:2px 10px;border-left:2px solid var(--line);color:var(--dim)}
243
+ .md hr{border:0;border-top:1px solid var(--line);margin:8px 0}
244
+ .md h1,.md h2,.md h3{font-size:12.5px;margin:8px 0 4px;color:var(--accent);letter-spacing:.02em}
245
+ .md h1{font-size:13.5px}
246
+ .md p{margin:4px 0}
247
+ .md ul{margin:4px 0;padding-left:18px}
248
+ .md li{margin:2px 0}
249
+ .md code{display:inline;background:var(--bg);border:1px solid var(--line);border-radius:4px;padding:0 4px;
250
+ font:11px ui-monospace,Menlo,monospace;color:var(--ink);white-space:pre-wrap;max-height:none;margin:0}
251
+ .md pre{background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;margin:6px 0;
252
+ overflow-x:auto;font:11px/1.5 ui-monospace,Menlo,monospace;white-space:pre}
253
+ .md pre code{background:none;border:0;padding:0}
254
+ .md a{color:var(--accent)}
255
+ .md.clamp{max-height:180px;overflow:hidden;margin-top:6px;
256
+ -webkit-mask-image:linear-gradient(#000 65%,transparent);mask-image:linear-gradient(#000 65%,transparent)}
257
+
258
+ /* ── secret (cred) cards ── */
259
+ .credname{font:600 13px ui-monospace,Menlo,monospace}
260
+ .creddesc{font-size:12px;color:var(--dim);margin-top:4px}
261
+ .credrow{display:flex;gap:8px;align-items:center;margin-top:8px}
262
+ .stchip{font-size:10px;border-radius:5px;padding:1px 7px;letter-spacing:.08em;text-transform:uppercase;
263
+ border:1px solid;flex:none;font-weight:600}
264
+ .stchip.ok{color:var(--teal);border-color:var(--teal)}
265
+ .stchip.miss{color:var(--red);border-color:var(--red)}
266
+ a.mint{color:var(--accent);font-size:12px;text-decoration:none}
267
+ a.mint:hover{text-decoration:underline}
268
+ .colcard h3 .stchip,.colcard h3 a.mint{margin-left:8px}
269
+
270
+ /* ── cmd+j palette ── */
271
+ .palov{position:fixed;inset:0;background:#0009;z-index:100;display:flex;justify-content:center;align-items:flex-start;
272
+ padding-top:14vh}
273
+ .pal{width:min(560px,90vw);background:var(--panel);border:1px solid var(--line);border-radius:12px;
274
+ box-shadow:0 12px 40px #000a;overflow:hidden}
275
+ .pal input{width:100%;background:var(--bg);border:0;border-bottom:1px solid var(--line);color:var(--ink);
276
+ padding:12px 14px;font-size:14px;outline:none}
277
+ .prows{max-height:50vh;overflow-y:auto;padding:6px}
278
+ .prow{display:flex;align-items:center;gap:10px;padding:7px 10px;border-radius:8px;cursor:pointer;font-size:13px}
279
+ .prow.on{background:var(--panel2)}
280
+ .prow.none{color:var(--dim);cursor:default}
281
+ .prow .plabel{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
282
+ .prow .pchip{font-size:10px;color:var(--dim);background:var(--bg);border:1px solid var(--line);border-radius:5px;
283
+ padding:1px 6px;text-transform:uppercase;letter-spacing:.08em;flex:none}
284
+
285
+ .wrap{max-width:900px;width:100%}
286
+ .wrap h1{font-size:20px;margin:0}
287
+ .wrap h1 small{color:var(--dim);font-weight:400;font-size:13px;margin-left:10px}
288
+ .hbar{display:flex;align-items:center;gap:12px;margin:18px 0 12px}
37
289
  .note{font-size:12px;color:var(--dim)}
38
290
  pre{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px;overflow-x:auto;
39
291
  font:12.5px/1.55 ui-monospace,Menlo,monospace;white-space:pre-wrap}
40
292
  .ok{color:var(--teal)}.warn{color:var(--gold)}.fail{color:var(--red)}.dim{color:var(--dim)}
41
- .err{margin-top:26px;color:var(--red)}
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>shipkit hub</title>
6
+ <title>hub</title>
7
7
  <link rel="stylesheet" href="./hub.css" />
8
8
  </head>
9
9
  <body>
@@ -0,0 +1,24 @@
1
+ // md — the SAFE markdown renderer for truth-file bodies on cards.
2
+ // marked (GFM: tables, strikethrough, autolinks) → sanitize-html allowlist.
3
+ // dangerouslySetInnerHTML consumes ONLY this output: every tag outside the
4
+ // allowlist is stripped, hrefs are http/https only, and every surviving
5
+ // anchor opens a new tab with rel=noreferrer.
6
+ import { marked } from 'marked'
7
+ import sanitizeHtml from 'sanitize-html'
8
+
9
+ marked.use({ gfm: true, breaks: false, async: false })
10
+
11
+ export const renderMd = (src: string): string =>
12
+ sanitizeHtml(marked.parse(src) as string, {
13
+ allowedTags: [
14
+ 'h1', 'h2', 'h3', 'h4', 'p', 'ul', 'ol', 'li',
15
+ 'table', 'thead', 'tbody', 'tr', 'th', 'td',
16
+ 'code', 'pre', 'strong', 'em', 'b', 'i', 'a', 'br', 'blockquote', 'hr',
17
+ ],
18
+ allowedAttributes: { a: ['href', 'target', 'rel'] },
19
+ allowedSchemes: ['http', 'https'],
20
+ allowProtocolRelative: false,
21
+ transformTags: {
22
+ a: (tagName, attribs) => ({ tagName, attribs: { ...attribs, target: '_blank', rel: 'noreferrer' } }),
23
+ },
24
+ })