rvlib-shipkit 0.38.1 → 1.0.1
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 +379 -16
- package/README.md +26 -14
- package/package.json +2 -2
- package/src/assets/gen/comfyts-gen.ts +27 -10
- package/src/assets/gen/derived.ts +1 -1
- package/src/assets/gen/generate-roll.ts +1 -1
- package/src/assets/kit-config.ts +6 -6
- package/src/assets/run-assets.ts +14 -10
- package/src/assets/server/studio-server.ts +6 -5
- package/src/check.ts +343 -0
- package/src/{rules/SK001-package-json-shape.ts → checks/SK001-package-json-shape.check.ts} +44 -46
- package/src/checks/SK002-contracts-fulfillness.check.ts +24 -0
- package/src/checks/SK003-claude-md-shape.check.ts +66 -0
- package/src/checks/SK004-gitignore-policy.check.ts +309 -0
- package/src/{rules/SK005-rvlib-versions.ts → checks/SK005-rvlib-versions.check.ts} +49 -32
- package/src/{rules/SK006-npm-publish-safety.ts → checks/SK006-npm-publish-safety.check.ts} +25 -24
- package/src/{rules/SK007-github-visibility.ts → checks/SK007-github-visibility.check.ts} +20 -14
- package/src/{rules/SK008-reviewer-agent.ts → checks/SK008-reviewer-agent.check.ts} +29 -14
- package/src/{rules/SK009-commit-ai-tells.ts → checks/SK009-commit-ai-tells.check.ts} +24 -21
- package/src/checks/SK010-undeclared-unit.check.ts +111 -0
- package/src/{rules/SK011-sibling-unit-ranges.ts → checks/SK011-sibling-unit-ranges.check.ts} +21 -20
- package/src/{rules/SK012-config-barrel-import.ts → checks/SK012-config-barrel-import.check.ts} +18 -23
- package/src/{rules/SK013-one-way-in-policy.ts → checks/SK013-one-way-in-policy.check.ts} +58 -50
- package/src/{rules/SK014-prose-ste.ts → checks/SK014-prose-ste.check.ts} +27 -13
- package/src/{rules/SK015-nested-repo.ts → checks/SK015-nested-repo.check.ts} +15 -12
- package/src/checks/SK016-legacy-check-shapes.check.ts +182 -0
- package/src/checks/SK017-vscode-hub-task.check.ts +197 -0
- package/src/checks/SK018-test-files-run.check.ts +28 -0
- package/src/checks/SK019-tests-run-in-units.check.ts +76 -0
- package/src/checks/SK020-self-spawning-check.check.ts +147 -0
- package/src/checks/contracts.check.ts +20 -0
- package/src/cli/cmd_Asks.ts +3 -2
- package/src/cli/cmd_Assets.ts +2 -2
- package/src/cli/cmd_Bump.ts +1 -1
- package/src/cli/cmd_Check.ts +6 -6
- package/src/cli/cmd_Checks.ts +95 -0
- package/src/cli/cmd_Ci.ts +48 -13
- package/src/cli/cmd_Commands.ts +2 -6
- package/src/cli/cmd_ConfigKeys.ts +69 -0
- package/src/cli/cmd_Contracts.ts +1 -1
- package/src/cli/cmd_Fleet.ts +16 -3
- package/src/cli/cmd_Hooks.ts +1 -1
- package/src/cli/cmd_Keys.ts +43 -18
- package/src/cli/cmd_Mv.ts +2 -2
- package/src/cli/cmd_Prose.ts +10 -1
- package/src/cli/cmd_Releases.ts +2 -2
- package/src/cli/cmd_Serve.ts +1 -1
- package/src/cli/cmd_Start.ts +6 -6
- package/src/cli/cmd_Status.ts +25 -0
- package/src/cli/cmd_Tui.ts +2 -2
- package/src/cli.ts +7 -4
- package/src/collections/actions.ts +4 -4
- package/src/collections/checks.ts +257 -0
- package/src/collections/collection-types.ts +254 -1
- package/src/collections/comfy.ts +23 -0
- package/src/collections/md-body.ts +14 -0
- package/src/collections/pings.ts +38 -12
- package/src/collections/posts.ts +19 -3
- package/src/collections/providers.ts +198 -91
- package/src/collections/reflections.ts +41 -0
- package/src/collections/state-rules.ts +73 -0
- package/src/collections/test-time.ts +288 -0
- package/src/collections/tests.ts +181 -0
- package/src/collections/wikis.ts +66 -0
- package/src/comfy/comfy-panel.ts +292 -0
- package/src/contracts/apply-contracts.ts +17 -12
- package/src/contracts/contract-types.ts +16 -4
- package/src/contracts/contracts-engine.ts +78 -42
- package/src/contracts/manifests/discord.contract.ts +6 -6
- package/src/contracts/manifests/identity.contract.ts +3 -3
- package/src/contracts/manifests/itch.contract.ts +12 -15
- package/src/contracts/manifests/social.contract.ts +5 -5
- package/src/contracts/manifests/steam.contract.ts +16 -16
- package/src/contracts/run-contracts.ts +4 -3
- package/src/core/attach.ts +96 -0
- package/src/core/canonical-scripts.ts +25 -0
- package/src/core/check-ctx.ts +204 -14
- package/src/core/check-file-names.ts +112 -0
- package/src/core/check-pass.ts +84 -0
- package/src/core/check-runner.ts +796 -0
- package/src/core/config-keys.ts +256 -0
- package/src/core/config-rewrite.ts +77 -0
- package/src/core/discover-checks.ts +166 -90
- package/src/core/discover-legacy-checks.ts +26 -0
- package/src/core/doc-block.ts +13 -0
- package/src/core/floor-resolve.ts +264 -0
- package/src/core/floor.ts +20 -137
- package/src/core/git-bin.ts +24 -0
- package/src/core/git-root.ts +108 -0
- package/src/core/instance.ts +195 -0
- package/src/core/jobs.ts +118 -0
- package/src/core/layout.ts +52 -0
- package/src/core/legacy-shapes.ts +207 -0
- package/src/core/lib-root.ts +5 -0
- package/src/core/locked-write.ts +110 -39
- package/src/core/machine-commit.ts +5 -4
- package/src/core/migrate-layout.ts +616 -0
- package/src/core/motherhome.ts +2 -8
- package/src/core/nesting.ts +4 -12
- package/src/core/one-way-in-policy.ts +73 -17
- package/src/core/outcome-words.ts +45 -0
- package/src/core/package-manager.ts +88 -0
- package/src/core/parallel.ts +21 -0
- package/src/core/process-tree.ts +44 -0
- package/src/core/raw-access.ts +93 -0
- package/src/core/read-set.ts +152 -0
- package/src/core/run-check.ts +155 -213
- package/src/core/run-ci.ts +152 -189
- package/src/core/run-fix.ts +99 -0
- package/src/core/run-fixers.ts +64 -33
- package/src/core/self-update.ts +11 -3
- package/src/core/slots.ts +83 -0
- package/src/core/spawn-timeout.ts +34 -3
- package/src/core/when-block.ts +401 -0
- package/src/core/when.ts +135 -0
- package/src/core/write-status.ts +5 -2
- package/src/fleet/scan-fleet.ts +16 -0
- package/src/hooks/leak-guard.ts +20 -9
- package/src/hooks/one-way-in-guard.ts +13 -5
- package/src/hooks/root-identity.ts +2 -1
- package/src/hooks/run-hooks.ts +38 -26
- package/src/init-repo.ts +39 -23
- package/src/ledger/check-stats.ts +155 -0
- package/src/ledger/check-table.ts +179 -0
- package/src/ledger/db.ts +255 -0
- package/src/ledger/gate-checks.ts +433 -0
- package/src/ledger/git-key.ts +116 -77
- package/src/ledger/git-scan.ts +96 -35
- package/src/ledger/ledger.ts +116 -42
- package/src/ledger/live-runs.ts +111 -0
- package/src/ledger/live-timeline.ts +97 -0
- package/src/ledger/repo-view.ts +73 -0
- package/src/ledger/run-log.ts +448 -0
- package/src/ledger/run-timeline.ts +173 -0
- package/src/ledger/store.ts +23 -12
- package/src/ledger/suite-log.ts +161 -0
- package/src/ledger/toolchain-identity.ts +7 -1
- package/src/ledger/world-cache.ts +111 -0
- package/src/mv/mv-md.ts +16 -6
- package/src/problems/extract.ts +182 -0
- package/src/problems/problem-board.ts +37 -0
- package/src/problems/problem-line.ts +77 -0
- package/src/prose/prose-view.ts +90 -0
- package/src/prose/run-prose.ts +60 -19
- package/src/prose/ste.ts +140 -24
- package/src/reflections/reflections-queue.ts +81 -0
- package/src/release/git-facts.ts +15 -9
- package/src/release/run-bump.ts +6 -5
- package/src/release/run-release.ts +25 -11
- package/src/release/run-releases.ts +2 -2
- package/src/release/unit-gate.ts +7 -54
- package/src/release/unit-resolve.ts +282 -0
- package/src/release/units.ts +33 -217
- package/src/repo-config.ts +157 -88
- package/src/rules/rule.ts +36 -96
- package/src/rules/untrack-generated.ts +5 -4
- package/src/scaffold/files/vscode-tasks.json +10 -31
- package/src/scaffold/jsonc-spans.ts +113 -0
- package/src/scaffold/project-scaffold.ts +16 -43
- package/src/scan.ts +98 -0
- package/src/serve/gate-trigger.ts +148 -0
- package/src/serve/health-schedule.ts +102 -0
- package/src/serve/hub-app/chart-view.ts +179 -0
- package/src/serve/hub-app/check-view.tsx +345 -0
- package/src/serve/hub-app/check-words.ts +164 -0
- package/src/serve/hub-app/checks-svc.ts +280 -0
- package/src/serve/hub-app/clock-svc.ts +33 -0
- package/src/serve/hub-app/col-card.tsx +23 -39
- package/src/serve/hub-app/cols-svc.ts +44 -20
- package/src/serve/hub-app/comfy-svc.ts +171 -0
- package/src/serve/hub-app/comfy-view.tsx +81 -0
- package/src/serve/hub-app/dur.ts +41 -0
- package/src/serve/hub-app/hash-route.ts +121 -0
- package/src/serve/hub-app/header-bar.tsx +117 -0
- package/src/serve/hub-app/health-svc.ts +23 -28
- package/src/serve/hub-app/help.tsx +29 -0
- package/src/serve/hub-app/hub-app.tsx +122 -119
- package/src/serve/hub-app/hub-st.ts +115 -42
- package/src/serve/hub-app/hub-types.ts +117 -2
- package/src/serve/hub-app/hub.css +534 -192
- package/src/serve/hub-app/index.html +2 -0
- package/src/serve/hub-app/live-svc.ts +174 -0
- package/src/serve/hub-app/md.ts +15 -0
- package/src/serve/hub-app/media-svc.ts +4 -1
- package/src/serve/hub-app/media-view.tsx +15 -0
- package/src/serve/hub-app/next-run.ts +68 -0
- package/src/serve/hub-app/project-gutter.css +65 -0
- package/src/serve/hub-app/project-gutter.tsx +128 -0
- package/src/serve/hub-app/projects-svc.ts +226 -0
- package/src/serve/hub-app/prose-hits-st.ts +124 -0
- package/src/serve/hub-app/prose-hits-view.tsx +176 -0
- package/src/serve/hub-app/prose-hits.css +21 -0
- package/src/serve/hub-app/run-strip.ts +47 -0
- package/src/serve/hub-app/runs-tabs.ts +24 -0
- package/src/serve/hub-app/test-view.tsx +107 -0
- package/src/serve/hub-app/tests-svc.ts +62 -0
- package/src/serve/hub-app/tests-view.tsx +238 -0
- package/src/serve/hub-app/time-ago.tsx +34 -0
- package/src/serve/hub-app/timeline-chart.tsx +274 -0
- package/src/serve/hub-app/timeline-geometry.ts +314 -0
- package/src/serve/hub-app/timeline-svc.ts +62 -0
- package/src/serve/hub-app/timeline-view.tsx +245 -0
- package/src/serve/hub-app/tree-build.ts +410 -0
- package/src/serve/hub-app/tree-row.tsx +42 -0
- package/src/serve/hub-app/tree-svc.ts +174 -0
- package/src/serve/hub-app/wikis-view.tsx +53 -0
- package/src/serve/other-repos.ts +79 -0
- package/src/serve/project-gutter-route.ts +350 -0
- package/src/serve/project-gutter.ts +449 -0
- package/src/serve/run-serve.ts +245 -16
- package/src/serve/same-origin.ts +14 -0
- package/src/serve/serve-hub.ts +213 -18
- package/src/shipkit.ts +19 -12
- package/src/smoke/run-smoke.ts +9 -4
- package/src/start-repo.ts +75 -36
- package/src/suite/judge-suites.ts +209 -15
- package/src/suite/kept-timings.ts +58 -0
- package/src/suite/runner-shapes.ts +88 -0
- package/src/suite/suite-cli.ts +147 -24
- package/src/suite/suite-cpu-preload.ts +92 -0
- package/src/suite/test-units.ts +243 -0
- package/src/ts/config.ts +4 -69
- package/src/ts/jsonc.ts +69 -0
- package/src/ts/lex.ts +143 -0
- package/src/ts/rename.ts +73 -10
- package/src/ts/source-graph.ts +28 -8
- package/src/ts/tsconfig-projects.ts +6 -97
- package/src/ts/tsconfig-resolve.ts +108 -0
- package/src/tui/build-steps-panel.ts +61 -0
- package/src/tui/checks-panel.ts +24 -16
- package/src/tui/run-tui.ts +2 -2
- package/src/tui/tui-panel.ts +5 -4
- package/src/wiki/graph-colors.ts +68 -0
- package/src/wiki/graph-mount.ts +7 -9
- package/src/wiki/graph-page.ts +42 -25
- package/src/wiki/graph-positions.ts +105 -0
- package/src/wiki/run-wiki.ts +1 -1
- package/src/wiki/scan-md.ts +1 -1
- package/src/wiki/serve-graph.ts +33 -3
- package/src/cli/cmd_Rules.ts +0 -51
- package/src/cli/cmd_Units.ts +0 -82
- package/src/collections/graphs.ts +0 -32
- package/src/contracts/contracts-once.ts +0 -43
- package/src/core/check-suite.ts +0 -302
- package/src/core/repair-contract-dirs.ts +0 -136
- package/src/core/task-runner.ts +0 -339
- package/src/ledger/gate-units.ts +0 -138
- package/src/ledger/unit-table.ts +0 -105
- package/src/rules/SK002-contracts-fulfillness.ts +0 -24
- package/src/rules/SK003-claude-md-shape.ts +0 -70
- package/src/rules/SK004-gitignore-policy.ts +0 -238
- package/src/rules/SK010-undeclared-release-unit.ts +0 -127
- package/src/rules/rule-registry.ts +0 -64
- package/src/rules/run-fix.ts +0 -72
- package/src/tui/tasks-panel.ts +0 -60
|
@@ -1,17 +1,28 @@
|
|
|
1
|
-
// Health service —
|
|
2
|
-
//
|
|
1
|
+
// Health service — the RAW view of the last run: STATUS.md as it sits on
|
|
2
|
+
// disk, plus the wire lines of a run happening now, streamed in.
|
|
3
|
+
//
|
|
4
|
+
// It is one node on the tree and nothing routes here by accident any more:
|
|
5
|
+
// every check has its own page (checks-svc.ts owns the run itself). This card
|
|
6
|
+
// exists so the whole text of a run stays reachable.
|
|
3
7
|
import { makeAutoObservable, runInAction } from 'mobx'
|
|
4
8
|
import type { HubSt } from './hub-st.ts'
|
|
5
9
|
|
|
6
10
|
export class HealthSvc {
|
|
7
11
|
statusMd: string | null = null
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
/** the lines of the run in flight, newest last */
|
|
13
|
+
live: string[] = []
|
|
10
14
|
|
|
11
15
|
constructor(private st: HubSt) {
|
|
12
16
|
makeAutoObservable<this, 'st'>(this, { st: false })
|
|
13
17
|
}
|
|
14
18
|
|
|
19
|
+
get running(): boolean {
|
|
20
|
+
return this.st.checks.busy
|
|
21
|
+
}
|
|
22
|
+
get note(): string {
|
|
23
|
+
return this.st.checks.note
|
|
24
|
+
}
|
|
25
|
+
|
|
15
26
|
async load(): Promise<void> {
|
|
16
27
|
try {
|
|
17
28
|
const s = (await (await fetch('/api/status.json')).json()) as { statusMd: string | null }
|
|
@@ -21,29 +32,13 @@ export class HealthSvc {
|
|
|
21
32
|
}
|
|
22
33
|
}
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
this.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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 })
|
|
35
|
+
appendLine(l: string): void {
|
|
36
|
+
runInAction(() => { this.live = [...this.live, l] })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** the whole health pass, through the one in-process runner */
|
|
40
|
+
async runHealthPass(): Promise<void> {
|
|
41
|
+
runInAction(() => { this.live = [] })
|
|
42
|
+
await this.st.checks.run()
|
|
48
43
|
}
|
|
49
44
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// THE help affordance, once, for every surface of the hub: a small `(?)` that
|
|
2
|
+
// shows one sentence the moment it is hovered or focused. CSS does the
|
|
3
|
+
// showing (.hlp in hub.css), so there is no library, no delay and no state.
|
|
4
|
+
//
|
|
5
|
+
// A term lives in TERMS rather than in the component that shows it, so the
|
|
6
|
+
// same word is explained the same way wherever it appears.
|
|
7
|
+
|
|
8
|
+
export const TERMS: Record<string, string> = {
|
|
9
|
+
always: 'it runs on every gate and every health pass',
|
|
10
|
+
release: 'it runs on the release path only: the full gate and the health pass run it, the quick gate does not',
|
|
11
|
+
health: 'it runs in the health pass (`shipkit check`) only, never on a gate and never on a commit',
|
|
12
|
+
off: 'it does not run at all, and the reason it was turned off rides in the repo config',
|
|
13
|
+
default: 'nobody declared where this check runs, so it takes the default',
|
|
14
|
+
advisory: 'it can fail without stopping anything: the gate reports it and goes on',
|
|
15
|
+
blocking: 'its failure stops the gate, and stops you',
|
|
16
|
+
ceiling: 'the time it is killed at: a check creeping toward its ceiling is visible before it trips',
|
|
17
|
+
cached: 'it did not run: the answer came from the cache, because nothing it depends on moved since it last ran',
|
|
18
|
+
ran: 'it ran and was timed, so the number is what it costs today',
|
|
19
|
+
slowest: 'the worst run on record, reds included',
|
|
20
|
+
average: 'the mean of the runs that really ran, cached ones left out',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** one sentence, either given here or named by its term. `text` takes a node
|
|
24
|
+
* so a legend can ride in the same bubble as a sentence */
|
|
25
|
+
export const Help = ({ text, term }: { text?: React.ReactNode; term?: string }): React.ReactElement => (
|
|
26
|
+
<span className="hlp" tabIndex={0} role="note">
|
|
27
|
+
?<span className="hbub">{text ?? (term != null ? TERMS[term] ?? term : '')}</span>
|
|
28
|
+
</span>
|
|
29
|
+
)
|
|
@@ -1,104 +1,79 @@
|
|
|
1
1
|
// THE page (Bun.serve HTML import — no vite, no build step). One surface
|
|
2
|
-
// per repo:
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// build/app-state-tree: HubSt is the single root
|
|
6
|
-
// own state, components read and call.
|
|
2
|
+
// per repo: ONE tree of every repo resource, by path or by type, coloured by
|
|
3
|
+
// state in the label itself, over one severity filter persisted at
|
|
4
|
+
// /?f=needs · /?f=red. A row opens its item view, an asset row opens the
|
|
5
|
+
// atelier. mobx+react per build/app-state-tree: HubSt is the single root
|
|
6
|
+
// (window.app), services own state, components read and call.
|
|
7
7
|
import { runInAction } from 'mobx'
|
|
8
8
|
import { observer } from 'mobx-react-lite'
|
|
9
9
|
import { StrictMode, useEffect } from 'react'
|
|
10
10
|
import { createRoot } from 'react-dom/client'
|
|
11
|
-
import { ColCard, ColGrid,
|
|
12
|
-
import {
|
|
11
|
+
import { ColCard, ColGrid, StatusCard } from './col-card.tsx'
|
|
12
|
+
import { WIKIS, WikisView } from './wikis-view.tsx'
|
|
13
|
+
import { ComfyOverlay, ComfyView } from './comfy-view.tsx'
|
|
14
|
+
import { HubSt, type Filter } from './hub-st.ts'
|
|
15
|
+
import { TimelineView } from './timeline-view.tsx'
|
|
16
|
+
import { HeaderBar, LIVE_KEY, LiveStrip } from './header-bar.tsx'
|
|
17
|
+
import { FOLLOW_KEY } from './timeline-chart.tsx'
|
|
18
|
+
import { CheckCard } from './check-view.tsx'
|
|
19
|
+
import { TestCard } from './test-view.tsx'
|
|
20
|
+
import { TestsPage } from './tests-view.tsx'
|
|
13
21
|
import { AssetStage, CatGrid, InfoChips, MediaTopbar, ToolsBar, VariantsBar } from './media-view.tsx'
|
|
14
|
-
import
|
|
22
|
+
import { TYPE_MARK } from './tree-build.ts'
|
|
23
|
+
import { TreeRowView } from './tree-row.tsx'
|
|
24
|
+
import { Help } from './help.tsx'
|
|
25
|
+
import { ProjectGutter } from './project-gutter.tsx'
|
|
15
26
|
|
|
16
27
|
const app = new HubSt()
|
|
17
28
|
// the root as a global — app code, console debugging (build/app-state-tree)
|
|
18
29
|
;(window as unknown as { app: HubSt }).app = app
|
|
19
30
|
app.boot()
|
|
20
31
|
|
|
21
|
-
// ──
|
|
22
|
-
|
|
23
|
-
<div className={`arow${a.id === app.media.curId ? ' sel' : ''}`} onClick={() => app.selectAsset(a.id)}>
|
|
24
|
-
<div className="lb"><b>{a.label}</b><span>{a.rolls} rolls</span></div>
|
|
25
|
-
{app.media.pending.includes(a.id)
|
|
26
|
-
? <div className="spin" />
|
|
27
|
-
: <div className={`dot ${a.stale ? 'stale' : a.picked ? 'done' : a.rolls > 0 ? 'some' : a.manual ? 'miss' : ''}`}
|
|
28
|
-
title={a.manual && a.rolls === 0 ? 'manual asset — nothing uploaded yet' : undefined} />}
|
|
29
|
-
</div>
|
|
30
|
-
))
|
|
32
|
+
// ── the tree: every resource of the repo, one row deep in its path ──────
|
|
33
|
+
// the row itself is tree-row.tsx, so a test renders it headless
|
|
31
34
|
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
if (m.dead != null) return <div className="gname"><span className="warnline">media API down: {m.dead}</span></div>
|
|
35
|
-
if (m.assets.length === 0) return null // kit-less repo — no media section, same page
|
|
35
|
+
const Tree = observer(() => {
|
|
36
|
+
const t = app.tree
|
|
36
37
|
return (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<div key={c}>
|
|
44
|
-
<div className="gname" id={`sec-cat-${c}`}>
|
|
45
|
-
<span>
|
|
46
|
-
<span className="fold" onClick={() => m.toggleFold(c)}>{open ? '▾' : '▸'}</span>{' '}
|
|
47
|
-
<span className="cn" title="grid view" onClick={() => app.showCat(c)}>{c}</span>
|
|
48
|
-
</span>
|
|
49
|
-
{inCat.every((a) => a.manual) ? null : (
|
|
50
|
-
<span className="catgen">{[1, 2, 3].map((n) => <button key={n} onClick={() => void m.genCat(c, n)}>+{n}</button>)}</span>
|
|
51
|
-
)}
|
|
52
|
-
</div>
|
|
53
|
-
{open ? inCat.map((a) => <AssetRow key={a.id} a={a} />) : null}
|
|
54
|
-
</div>
|
|
55
|
-
)
|
|
56
|
-
})}
|
|
57
|
-
</>
|
|
38
|
+
// roving focus: the arrows belong to the tree only while it HAS the
|
|
39
|
+
// focus, so the atelier keeps its own keys
|
|
40
|
+
<div className="tree" tabIndex={0} onKeyDown={(e) => { if (t.key(e.key)) e.preventDefault() }}>
|
|
41
|
+
{t.rows.map((r) => <TreeRowView key={r.key} app={app} row={r} />)}
|
|
42
|
+
{t.rows.length === 0 ? <div className="tempty">{app.filter === 'red' ? 'no reds — nothing is blocked ✨' : app.filter === 'needs' ? 'nothing needs you ✨' : 'no resources yet'}</div> : null}
|
|
43
|
+
</div>
|
|
58
44
|
)
|
|
59
45
|
})
|
|
60
46
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
<span className="cnt">{items.length}</span>
|
|
80
|
-
</div>
|
|
81
|
-
{open ? items.map((i) => (
|
|
82
|
-
// a health line's action IS the runner — an echo card would be a dead end
|
|
83
|
-
<div key={i.id} className={`arow${app.sel?.t === 'item' && app.sel.id === i.id ? ' sel' : ''}`}
|
|
84
|
-
onClick={() => (health ? app.showStatus() : c.select(col.id, i.id))}>
|
|
85
|
-
<div className="lb"><b>{i.text}</b>{i.file != null ? <span>{i.file}</span> : null}</div>
|
|
86
|
-
<div className={`cdot ${i.severity ?? ''}`} />
|
|
87
|
-
</div>
|
|
88
|
-
)) : null}
|
|
89
|
-
</div>
|
|
90
|
-
)
|
|
91
|
-
})}
|
|
92
|
-
</>
|
|
93
|
-
)
|
|
94
|
-
})
|
|
47
|
+
/** what every glyph in the type slot means, behind one `(?)` in the header */
|
|
48
|
+
const TypeLegend = () => (
|
|
49
|
+
<Help text={
|
|
50
|
+
<span className="legend">
|
|
51
|
+
{Object.entries(TYPE_MARK).map(([kind, m]) => (
|
|
52
|
+
<span key={kind} className="lrow"><i className={`ty ${m.hue}`}>{m.glyph}</i>{m.word}</span>
|
|
53
|
+
))}
|
|
54
|
+
</span>
|
|
55
|
+
} />
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const TreeTabs = observer(() => (
|
|
59
|
+
<span className="seg">
|
|
60
|
+
{(['path', 'type'] as const).map((t) => (
|
|
61
|
+
<button key={t} className={app.tree.tab === t ? 'on' : ''} onClick={() => app.tree.setTab(t)}>by {t}</button>
|
|
62
|
+
))}
|
|
63
|
+
</span>
|
|
64
|
+
))
|
|
95
65
|
|
|
96
66
|
// segmented severity filter — all · needs (yellow+red) · red, the address
|
|
97
67
|
// rides at /?f=… so a filtered view is shareable
|
|
68
|
+
// the shortcut letter rides INSIDE its own button, so the binding is
|
|
69
|
+
// discoverable without a legend
|
|
70
|
+
const FILTER_KEY = { all: 'a', needs: 'n', red: 'r' } as const
|
|
98
71
|
const SegFilter = observer(() => (
|
|
99
72
|
<span className="seg">
|
|
100
73
|
{(['all', 'needs', 'red'] as const).map((f) => (
|
|
101
|
-
<button key={f} className={app.filter === f ? 'on' : ''} onClick={() => app.setFilter(f)}>
|
|
74
|
+
<button key={f} className={app.filter === f ? 'on' : ''} onClick={() => app.setFilter(f)}>
|
|
75
|
+
{f}<span className="sk">{FILTER_KEY[f]}</span>
|
|
76
|
+
</button>
|
|
102
77
|
))}
|
|
103
78
|
</span>
|
|
104
79
|
))
|
|
@@ -106,43 +81,26 @@ const SegFilter = observer(() => (
|
|
|
106
81
|
const Sidebar = observer(() => (
|
|
107
82
|
<aside>
|
|
108
83
|
<div className="shead">
|
|
109
|
-
|
|
84
|
+
resources
|
|
110
85
|
<SegFilter />
|
|
111
86
|
</div>
|
|
112
87
|
<div className="ssearch">
|
|
113
|
-
<input placeholder="filter… (⌘J to jump)" value={app.query}
|
|
88
|
+
<input id="treeq" placeholder="filter… (/ to focus, ⌘J to jump)" value={app.query}
|
|
114
89
|
onChange={(e) => app.setQuery(e.target.value)}
|
|
115
90
|
onKeyDown={(e) => { if (e.key === 'Escape') app.setQuery('') }} />
|
|
116
91
|
</div>
|
|
117
|
-
<
|
|
118
|
-
|
|
92
|
+
<div className="stabs">
|
|
93
|
+
<TreeTabs />
|
|
94
|
+
<span className="sp" />
|
|
95
|
+
<TypeLegend />
|
|
96
|
+
<span className="cnt">{app.tree.visible.length}</span>
|
|
97
|
+
</div>
|
|
98
|
+
{app.tree.whenLine === '' ? null : <div className="swhen">{app.tree.whenLine}</div>}
|
|
99
|
+
{app.media.dead != null ? <div className="tempty"><span className="warnline">media API down: {app.media.dead}</span></div> : null}
|
|
100
|
+
<Tree />
|
|
119
101
|
</aside>
|
|
120
102
|
))
|
|
121
103
|
|
|
122
|
-
// ── the icon rail: quick jump to a section header (scroll + unfold,
|
|
123
|
-
// NEVER a navigation) — red dot = the section holds a red ──
|
|
124
|
-
const RAIL_ICON: Record<string, string> = { asks: '🙋', creds: '🔑', posts: '📣', health: '📊', graphs: '🕸' }
|
|
125
|
-
const Rail = observer(() => {
|
|
126
|
-
const c = app.cols
|
|
127
|
-
const m = app.media
|
|
128
|
-
// unfold flips state; the scroll waits for react to paint the rows
|
|
129
|
-
const jump = (id: string): void => { setTimeout(() => document.getElementById(id)?.scrollIntoView({ block: 'start' }), 0) }
|
|
130
|
-
return (
|
|
131
|
-
<nav className="skrail">
|
|
132
|
-
{m.assets.length > 0 ? (
|
|
133
|
-
<span className="ric" title="assets" onClick={() => { m.openAllCats(); if (m.cats[0] != null) jump(`sec-cat-${m.cats[0]}`) }}>
|
|
134
|
-
🖼{c.mediaHasRed ? <i /> : null}
|
|
135
|
-
</span>
|
|
136
|
-
) : null}
|
|
137
|
-
{c.sidebarCols.map((col) => (
|
|
138
|
-
<span key={col.id} className="ric" title={col.title} onClick={() => { c.openSection(col); jump(`sec-${col.id}`) }}>
|
|
139
|
-
{RAIL_ICON[col.id] ?? '•'}{c.hasRed(col) ? <i /> : null}
|
|
140
|
-
</span>
|
|
141
|
-
))}
|
|
142
|
-
</nav>
|
|
143
|
-
)
|
|
144
|
-
})
|
|
145
|
-
|
|
146
104
|
// ── cmd+j palette: every jump target behind one input ────────────────────
|
|
147
105
|
const Palette = observer(() => {
|
|
148
106
|
if (!app.palOpen) return null
|
|
@@ -173,7 +131,7 @@ const Palette = observer(() => {
|
|
|
173
131
|
)
|
|
174
132
|
})
|
|
175
133
|
|
|
176
|
-
// remark box on the ASSET view too: remarks land in .
|
|
134
|
+
// remark box on the ASSET view too: remarks land in .shipkit/assets/notes.md
|
|
177
135
|
// and come back as `remarks` (exchange both ways, media included)
|
|
178
136
|
const AssetRemarks = observer(() => {
|
|
179
137
|
const m = app.media
|
|
@@ -184,7 +142,7 @@ const AssetRemarks = observer(() => {
|
|
|
184
142
|
<div className="assetrem">
|
|
185
143
|
{remarks.length > 0 ? <div className="rem">{remarks.join('\n')}</div> : null}
|
|
186
144
|
<div className="row">
|
|
187
|
-
<input placeholder="remark → lands in .
|
|
145
|
+
<input placeholder="remark → lands in .shipkit/assets/notes.md, agents read it" value={c.remarkDraft}
|
|
188
146
|
onChange={(e) => runInAction(() => { c.remarkDraft = e.target.value })} />
|
|
189
147
|
<button className="ghost" onClick={() => void c.remark({ kind: 'media', id: m.curId })}>remark</button>
|
|
190
148
|
</div>
|
|
@@ -199,12 +157,21 @@ const Main = observer(() => {
|
|
|
199
157
|
if (app.sel?.t === 'item') return (
|
|
200
158
|
<main>
|
|
201
159
|
{errs}
|
|
202
|
-
{app.sel.col ===
|
|
203
|
-
? <
|
|
204
|
-
:
|
|
160
|
+
{app.sel.col === WIKIS
|
|
161
|
+
? <WikisView app={app} id={app.sel.id} />
|
|
162
|
+
: app.sel.col === 'comfy'
|
|
163
|
+
? <ComfyView app={app} id={app.sel.id} />
|
|
164
|
+
: app.sel.col === 'checks'
|
|
165
|
+
? <div className="stagewrap"><CheckCard app={app} id={app.sel.id} /></div>
|
|
166
|
+
: app.sel.col === 'tests'
|
|
167
|
+
? <div className="stagewrap"><TestCard app={app} id={app.sel.id} /></div>
|
|
168
|
+
: app.sel.col === 'runs'
|
|
169
|
+
? <div className="stagewrap"><TimelineView app={app} /></div>
|
|
170
|
+
: <div className="stagewrap"><ColCard app={app} col={app.sel.col} id={app.sel.id} /></div>}
|
|
205
171
|
</main>
|
|
206
172
|
)
|
|
207
173
|
if (app.sel?.t === 'col') return <main>{errs}<ColGrid app={app} id={app.sel.id} /></main>
|
|
174
|
+
if (app.sel?.t === 'tests') return <main>{errs}<div className="stagewrap"><TestsPage app={app} check={app.sel.check} /></div></main>
|
|
208
175
|
if (app.sel?.t === 'status') return <main>{errs}<div className="stagewrap"><StatusCard app={app} /></div></main>
|
|
209
176
|
if (app.sel?.t === 'cat') return <main>{errs}<CatGrid app={app} /></main>
|
|
210
177
|
if (app.sel?.t === 'asset') return (
|
|
@@ -218,22 +185,24 @@ const Main = observer(() => {
|
|
|
218
185
|
<VariantsBar app={app} />
|
|
219
186
|
</div>
|
|
220
187
|
<AssetRemarks />
|
|
221
|
-
<div className="help"><kbd>←</kbd><kbd>→</kbd> roll · <kbd>Enter</kbd> pick · <kbd
|
|
188
|
+
<div className="help"><kbd>←</kbd><kbd>→</kbd> roll · <kbd>Enter</kbd> pick · <kbd>⇧A</kbd> alt · <kbd>X</kbd> discard — picks ship via the kit's ship hook when present · category title = grid view</div>
|
|
222
189
|
</main>
|
|
223
190
|
)
|
|
224
191
|
// a filtered view with zero hits is the WIN state — say so, never point at a void
|
|
225
|
-
const allClear = app.filter !== 'all' &&
|
|
192
|
+
const allClear = app.filter !== 'all' && app.tree.visible.length === 0
|
|
226
193
|
return (
|
|
227
194
|
<main>
|
|
228
195
|
{errs}
|
|
229
196
|
<div className="stagewrap"><div className="ph">
|
|
230
197
|
{allClear ? (app.filter === 'red' ? 'no reds — nothing is blocked ✨' : 'all clear — nothing needs you ✨')
|
|
231
|
-
:
|
|
198
|
+
: 'Pick a row on the left.'}
|
|
232
199
|
</div></div>
|
|
233
200
|
</main>
|
|
234
201
|
)
|
|
235
202
|
})
|
|
236
203
|
|
|
204
|
+
const FILTER_BY_KEY: Record<string, Filter | undefined> = { a: 'all', n: 'needs', r: 'red' }
|
|
205
|
+
|
|
237
206
|
const App = observer(() => {
|
|
238
207
|
useEffect(() => {
|
|
239
208
|
const onKey = (e: KeyboardEvent): void => {
|
|
@@ -246,18 +215,52 @@ const App = observer(() => {
|
|
|
246
215
|
const t = e.target as HTMLElement
|
|
247
216
|
if (t.tagName === 'TEXTAREA' || t.tagName === 'INPUT') return
|
|
248
217
|
if (app.palOpen) return // the palette input owns its keys
|
|
249
|
-
|
|
250
|
-
|
|
218
|
+
// the atelier owns its roll keys while an asset is open, and
|
|
219
|
+
// says so by consuming them
|
|
220
|
+
if (app.sel?.t === 'asset' && app.media.key(e.key)) {
|
|
221
|
+
e.preventDefault()
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
if (e.key === '/') {
|
|
225
|
+
e.preventDefault()
|
|
226
|
+
document.getElementById('treeq')?.focus()
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return
|
|
230
|
+
if (e.key === LIVE_KEY) {
|
|
231
|
+
e.preventDefault()
|
|
232
|
+
app.live.toggle()
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
if (e.key === FOLLOW_KEY && !app.live.chart.follow) {
|
|
236
|
+
e.preventDefault()
|
|
237
|
+
app.live.chart.attach()
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
const f = FILTER_BY_KEY[e.key.toLowerCase()]
|
|
241
|
+
if (f != null) {
|
|
242
|
+
e.preventDefault()
|
|
243
|
+
app.setFilter(f)
|
|
244
|
+
}
|
|
251
245
|
}
|
|
252
246
|
document.addEventListener('keydown', onKey)
|
|
253
247
|
return () => document.removeEventListener('keydown', onKey)
|
|
254
248
|
}, [])
|
|
255
249
|
return (
|
|
250
|
+
// a flex ROW: a column mounted before `.appcol` sits left of the
|
|
251
|
+
// header and everything under it
|
|
256
252
|
<div className="app">
|
|
257
|
-
<
|
|
258
|
-
<
|
|
259
|
-
|
|
253
|
+
<ProjectGutter app={app} />
|
|
254
|
+
<div className="appcol">
|
|
255
|
+
<HeaderBar app={app} />
|
|
256
|
+
<LiveStrip app={app} />
|
|
257
|
+
<div className="appbody">
|
|
258
|
+
<Sidebar />
|
|
259
|
+
<Main />
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
260
262
|
<Palette />
|
|
263
|
+
<ComfyOverlay app={app} />
|
|
261
264
|
</div>
|
|
262
265
|
)
|
|
263
266
|
})
|