rvlib-shipkit 0.11.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 (60) hide show
  1. package/CHANGELOG.md +166 -0
  2. package/README.md +17 -3
  3. package/package.json +5 -2
  4. package/src/asks/asks-queue.ts +8 -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_Reflect.ts +116 -0
  12. package/src/cli/cmd_Rules.ts +32 -0
  13. package/src/cli/cmd_Serve.ts +1 -1
  14. package/src/cli.ts +5 -2
  15. package/src/collections/actions.ts +19 -0
  16. package/src/collections/collection-types.ts +13 -0
  17. package/src/collections/graphs.ts +34 -0
  18. package/src/collections/pings.ts +5 -1
  19. package/src/collections/posts.ts +20 -1
  20. package/src/collections/providers.ts +163 -50
  21. package/src/contracts/apply-contracts.ts +47 -0
  22. package/src/contracts/run-contracts.ts +8 -3
  23. package/src/core/canonical-scripts.ts +46 -0
  24. package/src/core/check-suite.ts +6 -3
  25. package/src/core/locked-write.ts +39 -0
  26. package/src/core/motherhome.ts +87 -0
  27. package/src/core/run-check.ts +28 -15
  28. package/src/core/run-ci.ts +4 -0
  29. package/src/core/write-status.ts +7 -26
  30. package/src/init-repo.ts +24 -11
  31. package/src/reflect/prompt-corpus.ts +305 -0
  32. package/src/repo-config.ts +20 -38
  33. package/src/rules/SK001-package-json-shape.ts +156 -0
  34. package/src/rules/SK002-contracts-fulfillness.ts +19 -0
  35. package/src/rules/SK003-claude-md-shape.ts +64 -0
  36. package/src/rules/SK004-gitignore-policy.ts +105 -0
  37. package/src/rules/SK005-rvlib-versions.ts +99 -0
  38. package/src/rules/SK006-npm-publish-safety.ts +42 -0
  39. package/src/rules/SK007-github-visibility.ts +63 -0
  40. package/src/rules/rule-registry.ts +32 -0
  41. package/src/rules/rule.ts +62 -0
  42. package/src/rules/run-fix.ts +57 -0
  43. package/src/serve/hub-app/col-card.tsx +192 -0
  44. package/src/serve/hub-app/cols-svc.ts +136 -0
  45. package/src/serve/hub-app/geometry-view.tsx +155 -0
  46. package/src/serve/hub-app/health-svc.ts +49 -0
  47. package/src/serve/hub-app/hub-app.tsx +236 -215
  48. package/src/serve/hub-app/hub-st.ts +169 -0
  49. package/src/serve/hub-app/hub-types.ts +72 -0
  50. package/src/serve/hub-app/hub.css +286 -59
  51. package/src/serve/hub-app/index.html +1 -1
  52. package/src/serve/hub-app/md.ts +24 -0
  53. package/src/serve/hub-app/media-svc.ts +429 -0
  54. package/src/serve/hub-app/media-view.tsx +299 -0
  55. package/src/serve/run-serve.ts +23 -9
  56. package/src/serve/serve-hub.ts +62 -47
  57. package/src/shipkit.ts +9 -5
  58. package/src/assets/server/studio-page.ts +0 -687
  59. package/src/core/package-json-check.ts +0 -88
  60. package/src/serve/hub-pages.ts +0 -38
@@ -0,0 +1,155 @@
1
+ // Geometry layer — the ground-rect oval + legs overlay on the stage image,
2
+ // plus the rectbar. Ported 1:1 from the deleted vanilla studio SPA: same
3
+ // drag math (move 1:1; resize anchored left-tip + center-y, legs scale),
4
+ // same 250ms debounced save through MediaSvc. Pointer-capture drags mutate
5
+ // the observable rect inside runInAction; the render derives every style
6
+ // from (rect × measured image box).
7
+ import { runInAction } from 'mobx'
8
+ import { observer } from 'mobx-react-lite'
9
+ import { useRef } from 'react'
10
+ import type { HubSt } from './hub-st.ts'
11
+ import type { Leg } from './hub-types.ts'
12
+
13
+ export type Box = { scale: number; x: number; y: number }
14
+
15
+ /** vanilla imgBox(): where the letterboxed image actually sits in the stage */
16
+ export function measureBox(stage: HTMLElement | null, img: HTMLImageElement | null): Box | null {
17
+ if (stage == null || img == null || !img.naturalWidth) return null
18
+ const b = img.getBoundingClientRect()
19
+ const st = stage.getBoundingClientRect()
20
+ const scale = Math.min(b.width / img.naturalWidth, b.height / img.naturalHeight)
21
+ const w = img.naturalWidth * scale
22
+ const h = img.naturalHeight * scale
23
+ return { scale, x: b.left - st.left + (b.width - w) / 2, y: b.top - st.top + (b.height - h) / 2 }
24
+ }
25
+
26
+ const LEG_TAGS = ['L·front', 'L·back', 'R·back', 'R·front']
27
+
28
+ type DragStart = { px: number; py: number; x: number; y: number; w: number; legs: Leg[]; cy: number; mode: 'move' | 'resize' }
29
+
30
+ const LegBox = observer(({ app, box, i, leg }: { app: HubSt; box: Box; i: number; leg: Leg }) => {
31
+ const m = app.media
32
+ const start = useRef<{ px: number; py: number; legs: Leg[]; mode: 'move' | 'resize' } | null>(null)
33
+ const px = (v: number): number => v * box.scale
34
+ const far = i < 2
35
+ const rect = m.rect!
36
+ return (
37
+ <>
38
+ <div
39
+ className={`leg ${far ? 'far' : 'near'}`}
40
+ style={{ color: far ? 'var(--blue)' : 'var(--red)', left: box.x + px(leg.x), top: box.y + px(leg.y), width: px(leg.w), height: px(leg.h) }}
41
+ onPointerDown={(e) => {
42
+ if (rect.legs == null) return
43
+ start.current = { px: e.clientX, py: e.clientY, legs: rect.legs.map((l) => ({ ...l })), mode: (e.target as HTMLElement).classList.contains('lh') ? 'resize' : 'move' }
44
+ ;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
45
+ e.preventDefault()
46
+ e.stopPropagation()
47
+ }}
48
+ onPointerMove={(e) => {
49
+ const s = start.current
50
+ if (s == null || rect.legs == null) return
51
+ const dx = (e.clientX - s.px) / box.scale
52
+ const dy = (e.clientY - s.py) / box.scale
53
+ runInAction(() => {
54
+ if (s.mode === 'move') {
55
+ const o = s.legs[i]!
56
+ rect.legs![i] = { ...o, x: o.x + dx, y: o.y + dy }
57
+ } else {
58
+ // one size for all 4 — every leg keeps its own FOOT planted
59
+ const o = s.legs[i]!
60
+ const w = Math.max(6, o.w + dx)
61
+ const h = Math.max(6, o.h - dy)
62
+ rect.legs = s.legs.map((l) => ({ x: l.x + (l.w - w) / 2, y: l.y + l.h - h, w, h }))
63
+ }
64
+ })
65
+ m.saveRect()
66
+ }}
67
+ onPointerUp={() => { start.current = null; m.saveRect() }}
68
+ >
69
+ <div className="hip" />
70
+ <div className="foot" />
71
+ <div className="tag">{LEG_TAGS[i]}</div>
72
+ {i === 0 ? <div className="lh" /> : null}
73
+ </div>
74
+ <div
75
+ className={`pill${far ? '' : ' near'}`}
76
+ style={{ left: box.x + px(leg.x), top: box.y + px(leg.y), width: px(leg.w), height: px(leg.h), background: rect.legColor ?? '#c8a070' }}
77
+ />
78
+ </>
79
+ )
80
+ })
81
+
82
+ export const GeometryLayer = observer(({ app, box }: { app: HubSt; box: Box | null }) => {
83
+ const m = app.media
84
+ const start = useRef<DragStart | null>(null)
85
+ const rect = m.rect
86
+ if (!m.hasGeom || rect == null || box == null) return null
87
+ const px = (v: number): number => v * box.scale
88
+ return (
89
+ <>
90
+ <div className="plumb" style={{ display: 'block', left: box.x + px(rect.x) }} />
91
+ <div className="plumb" style={{ display: 'block', left: box.x + px(rect.x + rect.w) }} />
92
+ <div
93
+ id="ov"
94
+ style={{ display: 'block', left: box.x + px(rect.x), top: box.y + px(rect.y), width: px(rect.w), height: px(rect.h) }}
95
+ onPointerDown={(e) => {
96
+ start.current = {
97
+ px: e.clientX, py: e.clientY, x: rect.x, y: rect.y, w: rect.w,
98
+ legs: (rect.legs ?? []).map((l) => ({ ...l })), cy: rect.y + rect.h / 2,
99
+ mode: (e.target as HTMLElement).classList.contains('hdl') ? 'resize' : 'move',
100
+ }
101
+ ;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
102
+ e.preventDefault()
103
+ }}
104
+ onPointerMove={(e) => {
105
+ const s = start.current
106
+ if (s == null) return
107
+ const dx = (e.clientX - s.px) / box.scale
108
+ const dy = (e.clientY - s.py) / box.scale
109
+ runInAction(() => {
110
+ if (s.mode === 'move') {
111
+ rect.x = Math.max(0, s.x + dx)
112
+ rect.y = Math.max(0, s.y + dy)
113
+ if (s.legs.length > 0) rect.legs = s.legs.map((l) => ({ ...l, x: l.x + (rect.x - s.x), y: l.y + (rect.y - s.y) }))
114
+ } else {
115
+ // anchored: left edge + center-y; legs scale around the same anchor
116
+ rect.w = Math.max(20, s.w + dx)
117
+ rect.h = rect.w * m.rectRatio
118
+ rect.y = s.cy - rect.h / 2
119
+ const f = rect.w / s.w
120
+ if (s.legs.length > 0) rect.legs = s.legs.map((l) => ({ x: s.x + (l.x - s.x) * f, y: s.cy + (l.y - s.cy) * f, w: l.w * f, h: l.h * f }))
121
+ }
122
+ })
123
+ m.saveRect()
124
+ }}
125
+ onPointerUp={() => { start.current = null; m.saveRect() }}
126
+ >
127
+ <div className="hdl" />
128
+ <div className="arrow" />
129
+ <div className="sz">{+rect.size.toFixed(2)}m</div>
130
+ </div>
131
+ {(rect.legs ?? []).map((l, i) => <LegBox key={i} app={app} box={box} i={i} leg={l} />)}
132
+ </>
133
+ )
134
+ })
135
+
136
+ export const Rectbar = observer(({ app }: { app: HubSt }) => {
137
+ const m = app.media
138
+ const rect = m.rect
139
+ if (!m.hasGeom || rect == null) return null
140
+ const cx = Math.round(rect.x + rect.w / 2)
141
+ const cy = Math.round(rect.y + rect.h / 2)
142
+ return (
143
+ <div id="rectbar" style={{ display: 'flex' }}>
144
+ <label>size <input type="number" step={0.1} min={0.1} value={rect.size}
145
+ onChange={(e) => { runInAction(() => { rect.size = Math.max(0.1, +e.target.value || rect.size) }); m.saveRect() }} /></label>
146
+ <button className="ghost" onClick={() => void m.legsDefault()}>legs</button>
147
+ <input type="color" title="leg color" value={/^#[0-9a-f]{6}$/i.test(rect.legColor ?? '') ? rect.legColor : '#c8a070'}
148
+ onChange={(e) => { runInAction(() => { rect.legColor = e.target.value }); m.saveRect() }} />
149
+ {rect.legs != null
150
+ ? <button className="ghost" title="remove legs" onClick={() => { if (confirm('remove all 4 legs?')) m.removeLegs() }}>✕</button>
151
+ : null}
152
+ <span className="ro">cx{cx} cy{cy} w{Math.round(rect.w)}px</span>
153
+ </div>
154
+ )
155
+ })
@@ -0,0 +1,49 @@
1
+ // Health service — STATUS.md + the streaming check runner (the health
2
+ // section's card in THE page).
3
+ import { makeAutoObservable, runInAction } from 'mobx'
4
+ import type { HubSt } from './hub-st.ts'
5
+
6
+ export class HealthSvc {
7
+ statusMd: string | null = null
8
+ running = false
9
+ note = ''
10
+
11
+ constructor(private st: HubSt) {
12
+ makeAutoObservable<this, 'st'>(this, { st: false })
13
+ }
14
+
15
+ async load(): Promise<void> {
16
+ try {
17
+ const s = (await (await fetch('/api/status.json')).json()) as { statusMd: string | null }
18
+ runInAction(() => { this.statusMd = s.statusMd })
19
+ } catch (e) {
20
+ runInAction(() => { this.statusMd = `FAIL status fetch broke: ${(e as Error).message}` })
21
+ }
22
+ }
23
+
24
+ async runCheck(): Promise<void> {
25
+ this.running = true
26
+ this.note = 'running…'
27
+ let text = ''
28
+ try {
29
+ const r = await fetch('/api/check/run', { method: 'POST' })
30
+ const rd = r.body!.getReader()
31
+ const dec = new TextDecoder()
32
+ for (;;) {
33
+ const { done, value } = await rd.read()
34
+ if (done) break
35
+ text += dec.decode(value, { stream: true })
36
+ runInAction(() => { this.statusMd = text })
37
+ }
38
+ runInAction(() => { this.note = 'done — STATUS.md refreshed on disk' })
39
+ void this.load()
40
+ void this.st.cols.poll()
41
+ } catch (e) {
42
+ runInAction(() => {
43
+ this.statusMd = `${text}\nFAIL stream broke: ${(e as Error).message}`
44
+ this.note = 'stream broke'
45
+ })
46
+ }
47
+ runInAction(() => { this.running = false })
48
+ }
49
+ }