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.
- package/CHANGELOG.md +166 -0
- package/README.md +17 -3
- package/package.json +5 -2
- package/src/asks/asks-queue.ts +8 -0
- package/src/assets/kit-config.ts +2 -3
- package/src/assets/roll-workspace.ts +2 -1
- package/src/assets/run-assets.ts +0 -11
- package/src/assets/server/studio-server.ts +7 -24
- package/src/cli/cmd_Assets.ts +3 -14
- package/src/cli/cmd_Check.ts +22 -4
- package/src/cli/cmd_Reflect.ts +116 -0
- package/src/cli/cmd_Rules.ts +32 -0
- package/src/cli/cmd_Serve.ts +1 -1
- package/src/cli.ts +5 -2
- package/src/collections/actions.ts +19 -0
- package/src/collections/collection-types.ts +13 -0
- package/src/collections/graphs.ts +34 -0
- package/src/collections/pings.ts +5 -1
- package/src/collections/posts.ts +20 -1
- package/src/collections/providers.ts +163 -50
- package/src/contracts/apply-contracts.ts +47 -0
- package/src/contracts/run-contracts.ts +8 -3
- package/src/core/canonical-scripts.ts +46 -0
- package/src/core/check-suite.ts +6 -3
- package/src/core/locked-write.ts +39 -0
- package/src/core/motherhome.ts +87 -0
- package/src/core/run-check.ts +28 -15
- package/src/core/run-ci.ts +4 -0
- package/src/core/write-status.ts +7 -26
- package/src/init-repo.ts +24 -11
- package/src/reflect/prompt-corpus.ts +305 -0
- package/src/repo-config.ts +20 -38
- package/src/rules/SK001-package-json-shape.ts +156 -0
- package/src/rules/SK002-contracts-fulfillness.ts +19 -0
- package/src/rules/SK003-claude-md-shape.ts +64 -0
- package/src/rules/SK004-gitignore-policy.ts +105 -0
- package/src/rules/SK005-rvlib-versions.ts +99 -0
- package/src/rules/SK006-npm-publish-safety.ts +42 -0
- package/src/rules/SK007-github-visibility.ts +63 -0
- package/src/rules/rule-registry.ts +32 -0
- package/src/rules/rule.ts +62 -0
- package/src/rules/run-fix.ts +57 -0
- package/src/serve/hub-app/col-card.tsx +192 -0
- package/src/serve/hub-app/cols-svc.ts +136 -0
- package/src/serve/hub-app/geometry-view.tsx +155 -0
- package/src/serve/hub-app/health-svc.ts +49 -0
- package/src/serve/hub-app/hub-app.tsx +236 -215
- package/src/serve/hub-app/hub-st.ts +169 -0
- package/src/serve/hub-app/hub-types.ts +72 -0
- package/src/serve/hub-app/hub.css +286 -59
- package/src/serve/hub-app/index.html +1 -1
- package/src/serve/hub-app/md.ts +24 -0
- package/src/serve/hub-app/media-svc.ts +429 -0
- package/src/serve/hub-app/media-view.tsx +299 -0
- package/src/serve/run-serve.ts +23 -9
- package/src/serve/serve-hub.ts +62 -47
- package/src/shipkit.ts +9 -5
- package/src/assets/server/studio-page.ts +0 -687
- package/src/core/package-json-check.ts +0 -88
- 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
|
+
}
|