@erclx/canon 5.77.0 → 5.78.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 +20 -0
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/references/ui-checklist.md +9 -1
- package/claude/skills/code-craft/REQUIREMENT.md +1 -1
- package/claude/skills/code-craft/SKILL.md +7 -0
- package/claude/skills/code-craft/references/adopted.md +8 -0
- package/claude/skills/pr-evidence/SKILL.md +6 -1
- package/claude/skills/pr-evidence/references/evidence.md +1 -0
- package/claude/skills/pr-evidence/references/reel.md +9 -1
- package/claude/skills/review-craft/references/ui.md +13 -1
- package/claude/skills/review-pr/references/body-variants.md +2 -0
- package/claude/skills/review-pr/references/close-out.md +2 -0
- package/claude/skills/review-pr/references/post.md +1 -0
- package/claude/skills/review-ui/REQUIREMENT.md +1 -0
- package/claude/skills/review-ui/SKILL.md +2 -2
- package/claude/skills/review-ui/references/driving.md +32 -4
- package/claude/skills/review-ui/references/post.md +6 -2
- package/claude/skills/role-orchestrator/SKILL.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-output.md +24 -1
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +3 -2
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +35 -4
- package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +1 -1
- package/claude/skills/role-worker/SKILL.md +1 -0
- package/claude/skills/test-craft/SKILL.md +1 -0
- package/claude/skills/ui-checklist/REQUIREMENT.md +1 -1
- package/claude/skills/ui-checklist/SKILL.md +2 -1
- package/claude/skills/ui-craft/references/capture.md +16 -0
- package/claude/skills/ui-film/REQUIREMENT.md +2 -2
- package/claude/skills/ui-film/SKILL.md +6 -1
- package/claude/skills/ui-film/references/compose-craft.md +68 -11
- package/claude/skills/ui-film/references/config.md +2 -2
- package/claude/skills/ui-film/references/handoff.md +1 -1
- package/claude/skills/ui-film/references/harness.md +71 -0
- package/claude/skills/ui-film/references/take-craft.md +42 -17
- package/docs/workflow/operating-model.md +6 -5
- package/docs/workflow/visual-design-workflow.md +2 -1
- package/governance/ledger/skills/code-craft/SKILL.md +9 -0
- package/governance/ledger/skills/test-craft/SKILL.md +1 -0
- package/governance/ledger/skills/ui-craft/capture.md +18 -5
- package/package.json +4 -3
- package/src/commands/pr.ts +117 -31
- package/src/commands/take.ts +12 -1
- package/src/design/components.ts +5 -107
- package/src/design/favicon.ts +21 -17
- package/src/design/render.ts +9 -27
- package/src/design/template.ts +42 -0
- package/src/design/templates/favicon.svg +17 -0
- package/src/design/templates/hand-drawn-figure.css +54 -0
- package/src/design/templates/preview.html +27 -0
- package/src/design/templates/scrollbar.css +29 -0
- package/src/design/templates/status.css +24 -0
- package/src/gate/embedded.ts +400 -0
- package/src/gate/stages.ts +8 -0
- package/src/pr/evidence.ts +43 -4
- package/src/pr/frames.ts +1 -6
- package/src/pr/lift.ts +77 -9
- package/src/pr/rest.ts +2 -0
- package/src/pr/review-scope.ts +28 -0
- package/src/pr/review-state.ts +5 -5
- package/src/pr/waiver.ts +45 -0
- package/src/take/camera.ts +337 -208
- package/src/take/film.ts +7 -1
- package/src/take/log.ts +43 -2
- package/src/take/perform.ts +85 -13
- package/src/take/plan.ts +26 -5
- package/src/take/reel.ts +30 -5
- package/src/take/screencast.ts +110 -3
- package/src/take/stepper.ts +24 -4
- package/src/commands/fixtures/gh.sh +0 -64
- package/src/take/fixtures/busy.html +0 -107
- package/src/take/fixtures/fade.html +0 -49
- package/src/take/fixtures/fetch.html +0 -38
- package/src/take/fixtures/input.html +0 -138
- package/src/take/zoom/NOTICE.md +0 -45
- package/src/take/zoom/math-utils.ts +0 -69
- package/src/take/zoom/region-utils.ts +0 -55
- package/src/take/zoom/spring.ts +0 -133
package/src/commands/pr.ts
CHANGED
|
@@ -43,6 +43,8 @@ import {
|
|
|
43
43
|
isEvidencePath,
|
|
44
44
|
PNG_HEADER_BYTES,
|
|
45
45
|
readChecklistBoxes,
|
|
46
|
+
countDrivable,
|
|
47
|
+
type ChecklistBox,
|
|
46
48
|
readOwed,
|
|
47
49
|
readPngWidth,
|
|
48
50
|
readUncaptured,
|
|
@@ -51,6 +53,7 @@ import {
|
|
|
51
53
|
settleChecklist,
|
|
52
54
|
} from '@/pr/evidence'
|
|
53
55
|
import { isWithinTolerance } from '@/pr/pixels'
|
|
56
|
+
import { REEL_MAX_BYTES } from '@/take/reel'
|
|
54
57
|
import {
|
|
55
58
|
dropFramesWithLegacy,
|
|
56
59
|
FRAMES_BRANCH,
|
|
@@ -64,7 +67,6 @@ import {
|
|
|
64
67
|
LEGACY_FRAMES_BRANCH,
|
|
65
68
|
pruneFramesWithLegacy,
|
|
66
69
|
pushFrame,
|
|
67
|
-
REEL_MAX_BYTES,
|
|
68
70
|
readFrame,
|
|
69
71
|
readReel,
|
|
70
72
|
isDesignReference,
|
|
@@ -83,6 +85,7 @@ import {
|
|
|
83
85
|
type PullState,
|
|
84
86
|
readBaseStanding,
|
|
85
87
|
readChangedPaths,
|
|
88
|
+
readsDrivableBox,
|
|
86
89
|
} from '@/pr/lift'
|
|
87
90
|
import { close, type CloseApi, type CloseRefusal } from '@/pr/close'
|
|
88
91
|
import {
|
|
@@ -171,7 +174,8 @@ interface ReadOptions {
|
|
|
171
174
|
|
|
172
175
|
interface EvidenceOptions extends ReadOptions {
|
|
173
176
|
readonly preview?: string
|
|
174
|
-
|
|
177
|
+
/** A path to fold in, or false from `--no-checklist` to drop the carried one. */
|
|
178
|
+
readonly checklist?: string | false
|
|
175
179
|
readonly local?: string
|
|
176
180
|
readonly reel?: string
|
|
177
181
|
readonly localSkip?: string
|
|
@@ -255,7 +259,7 @@ const TICK_REFUSALS: Record<TickReason | 'gh-failed', string> = {
|
|
|
255
259
|
'no-box':
|
|
256
260
|
'A named box is past the end of the checklist. Number boxes off `canon pr evidence --json`.',
|
|
257
261
|
'taste-box':
|
|
258
|
-
'A named box
|
|
262
|
+
'A named box carries (taste) among its trailing markers, which no driver passes. Nothing was written.',
|
|
259
263
|
'gh-failed':
|
|
260
264
|
'gh could not read or edit the marked comment on this pull request.',
|
|
261
265
|
}
|
|
@@ -438,7 +442,7 @@ type EvidenceRefusal =
|
|
|
438
442
|
|
|
439
443
|
const EVIDENCE_REFUSALS: Record<EvidenceRefusal, string> = {
|
|
440
444
|
'check-writes':
|
|
441
|
-
'--check is a read and renders no body, so it takes none of --preview, --local, --checklist, --reel, --local-skip, or --reel-skip. Run the check and the render as two calls.',
|
|
445
|
+
'--check is a read and renders no body, so it takes none of --preview, --local, --checklist, --no-checklist, --reel, --local-skip, or --reel-skip. Run the check and the render as two calls.',
|
|
442
446
|
'gh-failed':
|
|
443
447
|
'gh could not answer for this repository. Name the pull request number.',
|
|
444
448
|
'unreadable-checklist':
|
|
@@ -709,9 +713,11 @@ export function register(program: Command): void {
|
|
|
709
713
|
'',
|
|
710
714
|
'`ui` also carries the newest `## UI review` pass (`heading`, `state`,',
|
|
711
715
|
'`commit`, `source`), `isBound`, which is whether the evidence comment',
|
|
712
|
-
'carries a checklist
|
|
713
|
-
'
|
|
714
|
-
'
|
|
716
|
+
'carries a checklist holding a box a driver can run or the newest UI',
|
|
717
|
+
'pass is open, so a checklist of taste boxes alone binds nothing until',
|
|
718
|
+
'a pass opens, and `carriedFrom` when the lift carried a close',
|
|
719
|
+
'forward, or `drivenPaths` and the painted subset `paintedPaths` when',
|
|
720
|
+
'it refused to. An unbound family reads covered.',
|
|
715
721
|
'',
|
|
716
722
|
'Both families post as pull request reviews, so this reads',
|
|
717
723
|
'`pulls/<n>/reviews` alone, as the lift does. Issue comments are not',
|
|
@@ -772,6 +778,10 @@ export function register(program: Command): void {
|
|
|
772
778
|
'--checklist <path>',
|
|
773
779
|
'Close the body with this visual checklist file, from canon:ui-checklist',
|
|
774
780
|
)
|
|
781
|
+
.option(
|
|
782
|
+
'--no-checklist',
|
|
783
|
+
'Drop the checklist the marked comment carries, rendering the body without it',
|
|
784
|
+
)
|
|
775
785
|
.option(
|
|
776
786
|
'--local <url>',
|
|
777
787
|
"Add this worktree's running server to the address line, from canon pr local",
|
|
@@ -809,8 +819,9 @@ export function register(program: Command): void {
|
|
|
809
819
|
'Read `reason` on the JSON record before posting anything:',
|
|
810
820
|
' ok a body was rendered, with `commentId` set when a marked',
|
|
811
821
|
' comment already exists and should be edited in place',
|
|
812
|
-
' no-evidence nothing in the diff carries an evidence/ segment
|
|
813
|
-
' is
|
|
822
|
+
' no-evidence nothing in the diff carries an evidence/ segment and no',
|
|
823
|
+
' painted path is uncaptured, which is an ordinary,',
|
|
824
|
+
' silent no-op rather than a refusal',
|
|
814
825
|
' would-empty the render holds no cases and the marked comment holds',
|
|
815
826
|
' some, so no body is printed and a person edits the comment',
|
|
816
827
|
'',
|
|
@@ -853,6 +864,12 @@ export function register(program: Command): void {
|
|
|
853
864
|
'branch with a checklist and no evidence image renders a marked body',
|
|
854
865
|
'holding the checklist, so a later call finds and edits that comment.',
|
|
855
866
|
'',
|
|
867
|
+
'--no-checklist drops the checklist the marked comment carries, so the',
|
|
868
|
+
'body renders without it and the record reports no checklist fields.',
|
|
869
|
+
'A marked comment holding a checklist still renders as ok rather than',
|
|
870
|
+
'no-evidence, so the comment is edited rather than left showing it.',
|
|
871
|
+
'Of --checklist and --no-checklist, the last one passed wins.',
|
|
872
|
+
'',
|
|
856
873
|
'--local adds a **Local preview:** segment after any hosted one on the',
|
|
857
874
|
'address line, or carries the line alone, and is carried forward the',
|
|
858
875
|
'same way. It does not by itself turn no-evidence into ok, so a',
|
|
@@ -891,9 +908,13 @@ export function register(program: Command): void {
|
|
|
891
908
|
' checklist the checklist between its delimiters, ticks included',
|
|
892
909
|
' boxes each checklist box with its number, tick, stamp, and taste',
|
|
893
910
|
' mark, the numbering `canon pr tick` takes',
|
|
911
|
+
' drivable how many boxes a driver can run, every box but one whose',
|
|
912
|
+
' trailing markers hold (taste), in any order. Zero binds no',
|
|
913
|
+
' UI review, so `canon pr lift` and `review-state` leave the',
|
|
914
|
+
' UI family unbound',
|
|
894
915
|
'These come from the comment already posted, never from the flags this',
|
|
895
916
|
'call passed. A checklist the caller posted raw after a refused render',
|
|
896
|
-
'carries no marker, so the record reports none of
|
|
917
|
+
'carries no marker, so the record reports none of these for it. An',
|
|
897
918
|
'unread thread refuses as gh-failed on both reasons rather than',
|
|
898
919
|
'reporting the fields absent.',
|
|
899
920
|
'',
|
|
@@ -907,6 +928,14 @@ export function register(program: Command): void {
|
|
|
907
928
|
'unchecked rather than clean. It never adds to `owed`, since no step of',
|
|
908
929
|
'the ship chain can take a capture.',
|
|
909
930
|
'',
|
|
931
|
+
'A rendered body carries the list as an **Uncaptured:** line under the',
|
|
932
|
+
'addresses, each path in a code span. A non-empty list opens a comment',
|
|
933
|
+
'the way a checklist does, so a head whose only news is an uncaptured',
|
|
934
|
+
'path reports ok rather than no-evidence. The line is computed on every',
|
|
935
|
+
'render and never carried, so it drops once a capture empties the list',
|
|
936
|
+
'or a reel filmed at the head renders beside it. `canon pr lift`',
|
|
937
|
+
'refuses `painted-uncaptured` on the same list.',
|
|
938
|
+
'',
|
|
910
939
|
'--check reads the same changed set and thread, renders no body, and',
|
|
911
940
|
'reports what the ship chain still owes the pull request:',
|
|
912
941
|
' settled nothing is owed',
|
|
@@ -917,8 +946,8 @@ export function register(program: Command): void {
|
|
|
917
946
|
'request changed, and the marked comment carries no **Preview:**',
|
|
918
947
|
'address. A failed deploy reads the same as a skipped one.',
|
|
919
948
|
'The record carries `owed` and the same marked fields. --check refuses',
|
|
920
|
-
'with --preview, --local, --checklist, --
|
|
921
|
-
'--reel-skip as check-writes.',
|
|
949
|
+
'with --preview, --local, --checklist, --no-checklist, --reel,',
|
|
950
|
+
'--local-skip, or --reel-skip as check-writes.',
|
|
922
951
|
'',
|
|
923
952
|
'Refusals, read off `reason`:',
|
|
924
953
|
' unreadable-checklist --checklist named a path that could not be read,',
|
|
@@ -973,7 +1002,9 @@ export function register(program: Command): void {
|
|
|
973
1002
|
'they are read:',
|
|
974
1003
|
' review the newest `## Review` is closed at the head, off its marker',
|
|
975
1004
|
' ui the newest `## UI review` is closed at the head, bound only',
|
|
976
|
-
' where `canon pr evidence --check` carries a checklist
|
|
1005
|
+
' where `canon pr evidence --check` carries a checklist whose',
|
|
1006
|
+
' `drivable` count is not zero, a missing count binding. An',
|
|
1007
|
+
' open UI review refuses `ui-open` whatever the count. A close',
|
|
977
1008
|
' on an earlier commit still holds when it is an ancestor of the',
|
|
978
1009
|
' head and every path changed since is one no box can observe,',
|
|
979
1010
|
' being tests, stories, snapshots, markdown in the root and in',
|
|
@@ -984,7 +1015,19 @@ export function register(program: Command): void {
|
|
|
984
1015
|
' a box drives behavior a `.ts` file holds too. A close whose',
|
|
985
1016
|
' count line shows boxes not driven and none passed, failed,',
|
|
986
1017
|
' or needing eyes refuses `ui-undriven`, carried or not',
|
|
987
|
-
' evidence the evidence check reads settled',
|
|
1018
|
+
' evidence the evidence check reads settled, and every painted path',
|
|
1019
|
+
' it lists as `uncaptured` is covered. A reel the marked',
|
|
1020
|
+
' comment embeds at the head covers every path, and a',
|
|
1021
|
+
' `- **Waived capture:** `<path>`: <reason>` line outside a code',
|
|
1022
|
+
' fence covers the path it names as the list spells it. Only',
|
|
1023
|
+
' the newest `## Review` and the newest `## UI review` count,',
|
|
1024
|
+
' each closed by its own marker and not dismissed, so a pass',
|
|
1025
|
+
' must restate a waiver for it to hold on a later head. A',
|
|
1026
|
+
' review with no pass heading waives nothing. Any other path',
|
|
1027
|
+
' refuses `painted-uncaptured`,',
|
|
1028
|
+
' the gate entry naming what is left as `uncaptured` and what',
|
|
1029
|
+
' a review waived as `waived`, each sorted. An absent list,',
|
|
1030
|
+
' in a project whose base keeps no captures, passes',
|
|
988
1031
|
' standing the head is fresh against the base, not stale or conflicted',
|
|
989
1032
|
' ci every required check run on the tip passed, waited on up to --wait',
|
|
990
1033
|
'',
|
|
@@ -1014,7 +1057,8 @@ export function register(program: Command): void {
|
|
|
1014
1057
|
' lifted, already-ready, redrafted, clear (--check)',
|
|
1015
1058
|
' pull-closed, review-missing, review-open, review-unmarked,',
|
|
1016
1059
|
' review-behind, ui-missing, ui-open, ui-unmarked, ui-behind,',
|
|
1017
|
-
' ui-undriven, evidence-owed,
|
|
1060
|
+
' ui-undriven, evidence-owed, painted-uncaptured, standing-stale,',
|
|
1061
|
+
' standing-conflict, standing-unknown,',
|
|
1018
1062
|
' ci-pending (--check), ci-failing, ci-conflicted, ci-timeout,',
|
|
1019
1063
|
' head-moved, gh-failed',
|
|
1020
1064
|
'',
|
|
@@ -1367,7 +1411,7 @@ export function register(program: Command): void {
|
|
|
1367
1411
|
' nothing written, with `driven`, `head`, and `drivenPaths`',
|
|
1368
1412
|
' stale-head --head is behind the tip with no readable linear gap,',
|
|
1369
1413
|
' as a force-push or a rebase leaves, nothing written',
|
|
1370
|
-
' taste-box a named box
|
|
1414
|
+
' taste-box a named box carries (taste), nothing written',
|
|
1371
1415
|
' no-box a named number is past the checklist, nothing written',
|
|
1372
1416
|
' no-checklist the evidence comment carries no checklist',
|
|
1373
1417
|
' no-comment no comment carries the evidence marker',
|
|
@@ -2065,7 +2109,7 @@ async function listReviews(cwd: string, number: string) {
|
|
|
2065
2109
|
return listPages<RestReview>(
|
|
2066
2110
|
cwd,
|
|
2067
2111
|
`repos/{owner}/{repo}/pulls/${number}/reviews`,
|
|
2068
|
-
'id, body, commit_id, submitted_at',
|
|
2112
|
+
'id, body, commit_id, submitted_at, state',
|
|
2069
2113
|
)
|
|
2070
2114
|
}
|
|
2071
2115
|
|
|
@@ -2439,7 +2483,9 @@ function logReviewState(number: number, record: ReviewStateRecord): void {
|
|
|
2439
2483
|
else logWarn(line)
|
|
2440
2484
|
}
|
|
2441
2485
|
if (!record.ui.isBound) {
|
|
2442
|
-
logInfo(
|
|
2486
|
+
logInfo(
|
|
2487
|
+
'ui: the evidence carries no box a driver can run, so the family is unbound',
|
|
2488
|
+
)
|
|
2443
2489
|
}
|
|
2444
2490
|
}
|
|
2445
2491
|
|
|
@@ -2708,7 +2754,7 @@ async function runEvidence(
|
|
|
2708
2754
|
// Read ahead of anything else, so a bad path refuses before a `gh` round
|
|
2709
2755
|
// trip rather than after one.
|
|
2710
2756
|
let suppliedChecklist: string | undefined
|
|
2711
|
-
if (opts.checklist
|
|
2757
|
+
if (typeof opts.checklist === 'string') {
|
|
2712
2758
|
suppliedChecklist = await readFile(resolve(root, opts.checklist), 'utf8')
|
|
2713
2759
|
.then((text) => text.trim() || undefined)
|
|
2714
2760
|
.catch(() => undefined)
|
|
@@ -2794,7 +2840,10 @@ async function runEvidence(
|
|
|
2794
2840
|
commentId = findEvidenceCommentId(comments)
|
|
2795
2841
|
existingCases = findEvidenceCaseCount(comments)
|
|
2796
2842
|
carriedPreview = findEvidencePreview(comments)
|
|
2797
|
-
|
|
2843
|
+
// A dropped checklist is not carried, so neither the record nor the render
|
|
2844
|
+
// shows it, while the marked comment holding it still counts below.
|
|
2845
|
+
const postedChecklist = findEvidenceChecklist(comments)
|
|
2846
|
+
carriedChecklist = opts.checklist === false ? undefined : postedChecklist
|
|
2798
2847
|
carriedLocal = findEvidenceLocal(comments)
|
|
2799
2848
|
const carriedReel = findEvidenceReel(comments)
|
|
2800
2849
|
|
|
@@ -2804,10 +2853,7 @@ async function runEvidence(
|
|
|
2804
2853
|
...(carriedPreview !== undefined && { preview: carriedPreview }),
|
|
2805
2854
|
...(carriedLocal !== undefined && { local: carriedLocal }),
|
|
2806
2855
|
...(carriedReel !== undefined && { reel: carriedReel }),
|
|
2807
|
-
...(carriedChecklist !== undefined &&
|
|
2808
|
-
checklist: carriedChecklist,
|
|
2809
|
-
boxes: readChecklistBoxes(carriedChecklist),
|
|
2810
|
-
}),
|
|
2856
|
+
...(carriedChecklist !== undefined && checklistFields(carriedChecklist)),
|
|
2811
2857
|
}
|
|
2812
2858
|
|
|
2813
2859
|
// A deploy that resolves and does not build from any changed path makes a
|
|
@@ -2866,13 +2912,18 @@ async function runEvidence(
|
|
|
2866
2912
|
// A checklist opens a comment on its own, so it always lands in the marked
|
|
2867
2913
|
// comment a later call edits. A local address rides on a checklist or a
|
|
2868
2914
|
// hosted preview and never opens one alone, so a docs-only branch with a
|
|
2869
|
-
// server up posts nothing.
|
|
2915
|
+
// server up posts nothing. An uncaptured painted path opens one too, so the
|
|
2916
|
+
// gap the lift refuses on shows on the thread.
|
|
2917
|
+
// A dropped checklist still opens a render, so the marked comment holding
|
|
2918
|
+
// it is edited without the block rather than left showing it.
|
|
2870
2919
|
const hasChecklist =
|
|
2871
|
-
suppliedChecklist !== undefined ||
|
|
2920
|
+
suppliedChecklist !== undefined || postedChecklist !== undefined
|
|
2921
|
+
const hasUncaptured = uncaptured !== undefined && uncaptured.length > 0
|
|
2872
2922
|
if (
|
|
2873
2923
|
grouped.kind === 'refused' &&
|
|
2874
2924
|
opts.preview === undefined &&
|
|
2875
|
-
!hasChecklist
|
|
2925
|
+
!hasChecklist &&
|
|
2926
|
+
!hasUncaptured
|
|
2876
2927
|
) {
|
|
2877
2928
|
logStep('Skipped')
|
|
2878
2929
|
logInfo(
|
|
@@ -2933,6 +2984,7 @@ async function runEvidence(
|
|
|
2933
2984
|
local,
|
|
2934
2985
|
reel,
|
|
2935
2986
|
skips,
|
|
2987
|
+
uncaptured,
|
|
2936
2988
|
)
|
|
2937
2989
|
|
|
2938
2990
|
const caseCount = states.reduce((n, s) => n + s.items.length, 0)
|
|
@@ -3030,6 +3082,16 @@ async function runEvidence(
|
|
|
3030
3082
|
return 0
|
|
3031
3083
|
}
|
|
3032
3084
|
|
|
3085
|
+
/** The record fields a checklist carries, with `drivable` counting the boxes a driver can run. */
|
|
3086
|
+
function checklistFields(checklist: string): {
|
|
3087
|
+
checklist: string
|
|
3088
|
+
boxes: ChecklistBox[]
|
|
3089
|
+
drivable: number
|
|
3090
|
+
} {
|
|
3091
|
+
const boxes = readChecklistBoxes(checklist)
|
|
3092
|
+
return { checklist, boxes, drivable: countDrivable(boxes) }
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3033
3095
|
async function readWorkflows(root: string): Promise<WorkflowFile[]> {
|
|
3034
3096
|
const dir = join(root, '.github', 'workflows')
|
|
3035
3097
|
if (!existsSync(dir)) return []
|
|
@@ -4122,7 +4184,7 @@ const LIFT_REFUSALS: Record<LiftRefusal | 'bad-wait' | 'gh-missing', string> = {
|
|
|
4122
4184
|
'review-behind':
|
|
4123
4185
|
'The newest code review closed on an earlier commit than the head.',
|
|
4124
4186
|
'ui-missing':
|
|
4125
|
-
'The evidence comment carries a checklist and no UI review has been posted.',
|
|
4187
|
+
'The evidence comment carries a checklist holding a box a driver can run, and no UI review has been posted.',
|
|
4126
4188
|
'ui-open': 'The newest UI review is open, so a box still owes a fix.',
|
|
4127
4189
|
'ui-unmarked':
|
|
4128
4190
|
'The newest UI review carries no head marker, so the commit it covers is a GitHub stamp this gate does not trust.',
|
|
@@ -4132,6 +4194,8 @@ const LIFT_REFUSALS: Record<LiftRefusal | 'bad-wait' | 'gh-missing', string> = {
|
|
|
4132
4194
|
'The newest UI review closed having driven no box, so it looked at nothing. Re-drive the checklist on an address that answers.',
|
|
4133
4195
|
'evidence-owed':
|
|
4134
4196
|
'The evidence check reads owed, so the ship chain left a step undone.',
|
|
4197
|
+
'painted-uncaptured':
|
|
4198
|
+
'A painted path changed with no capture, no reel at the head, and no waiver in a review. Capture it, or have a review waive it.',
|
|
4135
4199
|
'standing-stale':
|
|
4136
4200
|
'The base moved under a path this branch writes since it branched, so CI measured a tree that is gone.',
|
|
4137
4201
|
'standing-conflict': 'The head does not merge cleanly into the base.',
|
|
@@ -4235,7 +4299,14 @@ async function readEvidenceGate(
|
|
|
4235
4299
|
.at(-1)
|
|
4236
4300
|
if (last === undefined) return undefined
|
|
4237
4301
|
|
|
4238
|
-
let record: {
|
|
4302
|
+
let record: {
|
|
4303
|
+
reason?: unknown
|
|
4304
|
+
owed?: unknown
|
|
4305
|
+
checklist?: unknown
|
|
4306
|
+
drivable?: unknown
|
|
4307
|
+
uncaptured?: unknown
|
|
4308
|
+
reel?: unknown
|
|
4309
|
+
}
|
|
4239
4310
|
try {
|
|
4240
4311
|
record = JSON.parse(last)
|
|
4241
4312
|
} catch {
|
|
@@ -4243,12 +4314,27 @@ async function readEvidenceGate(
|
|
|
4243
4314
|
}
|
|
4244
4315
|
if (record.reason !== 'settled' && record.reason !== 'owed') return undefined
|
|
4245
4316
|
|
|
4317
|
+
const reelHead = readReelHead(record.reel)
|
|
4246
4318
|
return {
|
|
4247
4319
|
isSettled: record.reason === 'settled',
|
|
4248
4320
|
owed: Array.isArray(record.owed) ? record.owed.map(String) : [],
|
|
4249
|
-
|
|
4250
|
-
|
|
4321
|
+
hasDrivableBox: readsDrivableBox(record),
|
|
4322
|
+
// Absent stays absent, since it means the base keeps no captures and
|
|
4323
|
+
// the gate passes as it did before the list existed.
|
|
4324
|
+
...(Array.isArray(record.uncaptured) && {
|
|
4325
|
+
uncaptured: record.uncaptured.map(String),
|
|
4326
|
+
}),
|
|
4327
|
+
...(reelHead !== undefined && { reelHead }),
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4331
|
+
function readReelHead(reel: unknown): string | undefined {
|
|
4332
|
+
if (typeof reel !== 'object' || reel === null || !('head' in reel)) {
|
|
4333
|
+
return undefined
|
|
4251
4334
|
}
|
|
4335
|
+
return typeof reel.head === 'string' && reel.head !== ''
|
|
4336
|
+
? reel.head
|
|
4337
|
+
: undefined
|
|
4252
4338
|
}
|
|
4253
4339
|
|
|
4254
4340
|
async function readChecksGate(
|
package/src/commands/take.ts
CHANGED
|
@@ -114,6 +114,9 @@ export function register(program: Command): void {
|
|
|
114
114
|
'WebP at 960 wide and 20 fps, keeping every third frame, with the cursor',
|
|
115
115
|
'and click ring drawn from each camera track. It carries no zoom, so a',
|
|
116
116
|
"reviewer sees the whole page. clip.mp4 stays each beat's raw take.",
|
|
117
|
+
'The reel is lossless, so a fade-out leaves no ghost, and it falls back',
|
|
118
|
+
'to lossy past 10 MB so a long reel still posts. The record says which',
|
|
119
|
+
'in `lossless`.',
|
|
117
120
|
'',
|
|
118
121
|
' {',
|
|
119
122
|
' "beats": [',
|
|
@@ -127,7 +130,8 @@ export function register(program: Command): void {
|
|
|
127
130
|
' }',
|
|
128
131
|
'',
|
|
129
132
|
'Step kinds:',
|
|
130
|
-
' move <target>|<x,y> glide the pointer to a target or a point',
|
|
133
|
+
' move <target>|<x,y> glide the pointer to a target or a point, and',
|
|
134
|
+
' with hold <ms>, zoom onto its end for that long',
|
|
131
135
|
' click <target> glide to the target and press it',
|
|
132
136
|
' dblclick <target> glide to the target and press it twice',
|
|
133
137
|
' drag <from> <to> press the from target, glide held, release on to',
|
|
@@ -143,6 +147,13 @@ export function register(program: Command): void {
|
|
|
143
147
|
'PageDown, the four arrows as ArrowUp and the like, and F1 to F12. A key',
|
|
144
148
|
'moves no pointer, so the camera neither aims at it nor ends a zoom.',
|
|
145
149
|
'',
|
|
150
|
+
'A take paces each press on its own: the pointer rests 300 ms on the',
|
|
151
|
+
'target before a click, dblclick, or drag, and holds 500 ms on the',
|
|
152
|
+
'result before the next step. A fill waits 400 ms between pressing the',
|
|
153
|
+
'field and its first key, and holds 500 ms after its last. Setup steps',
|
|
154
|
+
'run unpaced. The camera zooms in over the glide reaching a target and',
|
|
155
|
+
'out over the glide leaving it, scaling about the target.',
|
|
156
|
+
'',
|
|
146
157
|
'drag drives pointer events, the kind a canvas board listens for. It',
|
|
147
158
|
'starts no native HTML5 drag of a draggable element on the stepped route,',
|
|
148
159
|
'so a beat needing one films the same only on the screencast route.',
|
package/src/design/components.ts
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* names out of that record and says they live in code, which is here.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { readTemplate } from '@/design/template'
|
|
19
|
+
|
|
18
20
|
export interface Component {
|
|
19
21
|
readonly name: string
|
|
20
22
|
/** Why the component exists, carried into the emitted stylesheet as a comment. */
|
|
@@ -43,30 +45,7 @@ const STATUS: Component = {
|
|
|
43
45
|
'--color-muted',
|
|
44
46
|
'--radius-marker',
|
|
45
47
|
],
|
|
46
|
-
rules:
|
|
47
|
-
display: inline-flex;
|
|
48
|
-
align-items: center;
|
|
49
|
-
gap: 0.42rem;
|
|
50
|
-
font-size: 0.75rem;
|
|
51
|
-
font-weight: 500;
|
|
52
|
-
letter-spacing: 0;
|
|
53
|
-
text-transform: none;
|
|
54
|
-
color: var(--color-muted);
|
|
55
|
-
white-space: nowrap;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.status::before {
|
|
59
|
-
content: '';
|
|
60
|
-
width: 6px;
|
|
61
|
-
height: 6px;
|
|
62
|
-
flex: none;
|
|
63
|
-
border-radius: var(--radius-marker);
|
|
64
|
-
background: var(--color-border);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.status.is-done::before {
|
|
68
|
-
background: var(--color-accent);
|
|
69
|
-
}`,
|
|
48
|
+
rules: readTemplate('status.css'),
|
|
70
49
|
}
|
|
71
50
|
|
|
72
51
|
/**
|
|
@@ -84,35 +63,7 @@ const SCROLLBAR: Component = {
|
|
|
84
63
|
'so the two engines cannot drift apart.',
|
|
85
64
|
].join('\n '),
|
|
86
65
|
reads: ['--color-border', '--color-muted', '--color-background'],
|
|
87
|
-
rules:
|
|
88
|
-
scrollbar-width: thin;
|
|
89
|
-
scrollbar-color: var(--color-border) transparent;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
*::-webkit-scrollbar {
|
|
93
|
-
width: 10px;
|
|
94
|
-
height: 10px;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
*::-webkit-scrollbar-track {
|
|
98
|
-
background: transparent;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
*::-webkit-scrollbar-thumb {
|
|
102
|
-
border-radius: var(--radius-marker);
|
|
103
|
-
background: var(--color-border);
|
|
104
|
-
/* Inset by painting a border in the page color, which is what keeps the
|
|
105
|
-
thumb off the edges without a second element. */
|
|
106
|
-
border: 3px solid var(--color-background);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
*::-webkit-scrollbar-thumb:hover {
|
|
110
|
-
background: var(--color-muted);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
*::-webkit-scrollbar-corner {
|
|
114
|
-
background: transparent;
|
|
115
|
-
}`,
|
|
66
|
+
rules: readTemplate('scrollbar.css'),
|
|
116
67
|
}
|
|
117
68
|
|
|
118
69
|
/**
|
|
@@ -130,60 +81,7 @@ const HAND_DRAWN_FIGURE: Component = {
|
|
|
130
81
|
'reading measure and cleared past an outline rail on a wide viewport.',
|
|
131
82
|
].join('\n '),
|
|
132
83
|
reads: ['--color-muted', '--figure-hand', '--figure-wide'],
|
|
133
|
-
rules:
|
|
134
|
-
|
|
135
|
-
:root {
|
|
136
|
-
--figure-hand: 'Virgil', 'Excalifont', cursive;
|
|
137
|
-
--figure-wide: 64rem;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
figure {
|
|
141
|
-
margin: 2.75rem 0;
|
|
142
|
-
width: var(--figure-wide);
|
|
143
|
-
max-width: 92vw;
|
|
144
|
-
margin-left: 50%;
|
|
145
|
-
transform: translateX(-50%);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
figure svg {
|
|
149
|
-
width: 100%;
|
|
150
|
-
height: auto;
|
|
151
|
-
display: block;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/* CSS beats an SVG presentation attribute, so the diagrams pick up the
|
|
155
|
-
hand face without editing a single lesson. */
|
|
156
|
-
figure svg text {
|
|
157
|
-
font-family: var(--figure-hand);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
figcaption {
|
|
161
|
-
font-family: var(--figure-hand);
|
|
162
|
-
font-size: 1rem;
|
|
163
|
-
color: var(--color-muted);
|
|
164
|
-
margin: 1rem auto 0;
|
|
165
|
-
line-height: 1.5;
|
|
166
|
-
max-width: 52rem;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/* A figure breaks the measure and must still clear an outline rail. Deriving
|
|
170
|
-
the ceiling from the viewport does not hold, since a rail positioned from
|
|
171
|
-
the centre keeps overlapping as the window widens, so the ceiling is fixed
|
|
172
|
-
instead. */
|
|
173
|
-
@media (min-width: 1421px) {
|
|
174
|
-
figure {
|
|
175
|
-
max-width: 54rem;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
@media (max-width: 640px) {
|
|
180
|
-
figure {
|
|
181
|
-
width: 100%;
|
|
182
|
-
max-width: 100%;
|
|
183
|
-
margin-left: 0;
|
|
184
|
-
transform: none;
|
|
185
|
-
}
|
|
186
|
-
}`,
|
|
84
|
+
rules: readTemplate('hand-drawn-figure.css'),
|
|
187
85
|
}
|
|
188
86
|
|
|
189
87
|
export const COMPONENTS: readonly Component[] = [
|
package/src/design/favicon.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Relative, since web/ bundles this module for FAVICON_COLORS and its `@` means web/src.
|
|
2
|
+
import { fillTemplate, readTemplate } from './template'
|
|
3
|
+
|
|
1
4
|
export interface ThemePair {
|
|
2
5
|
readonly light: string
|
|
3
6
|
readonly dark: string
|
|
@@ -68,23 +71,24 @@ export function renderFavicon(mark: string, colors: FaviconColors): string {
|
|
|
68
71
|
const inset = EDGE_WIDTH / 2
|
|
69
72
|
const shapes = markShapes(mark)
|
|
70
73
|
|
|
71
|
-
return
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
74
|
+
return fillTemplate(readTemplate('favicon.svg'), {
|
|
75
|
+
left: String(left),
|
|
76
|
+
top: String(top),
|
|
77
|
+
side: String(round(side)),
|
|
78
|
+
tileX: String(round(left + inset)),
|
|
79
|
+
tileY: String(round(top + inset)),
|
|
80
|
+
tileSide: String(round(side - EDGE_WIDTH)),
|
|
81
|
+
tileRadius: String(round(side * TILE_RADIUS - inset)),
|
|
82
|
+
edgeWidth: String(EDGE_WIDTH),
|
|
83
|
+
tileDark: colors.tile.dark,
|
|
84
|
+
tileLight: colors.tile.light,
|
|
85
|
+
edge: colors.edge,
|
|
86
|
+
cDark: colors.c.dark,
|
|
87
|
+
cLight: colors.c.light,
|
|
88
|
+
pipDark: colors.pip.dark,
|
|
89
|
+
pipLight: colors.pip.light,
|
|
90
|
+
shapes: shapes.replace(/\n\s*/g, '\n '),
|
|
91
|
+
})
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
/**
|
package/src/design/render.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { fontFaceBlock } from '@/design/css'
|
|
|
6
6
|
import { faviconLink } from '@/design/favicon'
|
|
7
7
|
import type { FontFace } from '@/design/fonts'
|
|
8
8
|
import { FONT_FACES } from '@/design/fonts'
|
|
9
|
+
import { fillTemplate, readTemplate } from '@/design/template'
|
|
9
10
|
import { colorValue } from '@/design/tokens'
|
|
10
11
|
|
|
11
12
|
export interface RenderResult {
|
|
@@ -239,33 +240,14 @@ function buildHtml(
|
|
|
239
240
|
const summary = tagged
|
|
240
241
|
? `\n<p class="note">${total - tagged} of ${total} cells are anchored to a source. The other ${tagged} ${verb} <code>? verify</code>, so nothing anchors them yet.</p>`
|
|
241
242
|
: ''
|
|
242
|
-
return
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
body { font-family: ${bodyStack}; margin: 2rem; max-width: 960px; color: var(--preview-ink); background: var(--preview-paper); }
|
|
251
|
-
h1 { margin-top: 0; }
|
|
252
|
-
h2 { margin-top: 2rem; border-bottom: 1px solid var(--preview-rule); padding-bottom: 0.25rem; }
|
|
253
|
-
table { border-collapse: collapse; width: 100%; margin-top: 0.5rem; }
|
|
254
|
-
th, td { text-align: left; padding: 0.5rem 0.75rem; border-bottom: 1px solid var(--preview-rule); font-size: 14px; }
|
|
255
|
-
th { background: var(--preview-panel); font-weight: 600; }
|
|
256
|
-
.swatch { display: inline-block; width: 1.5rem; height: 1.5rem; border-radius: 4px; border: 1px solid var(--preview-rule); vertical-align: middle; margin-right: 0.5rem; }
|
|
257
|
-
.bar { display: inline-block; height: 1rem; background: var(--preview-muted); border-radius: 2px; vertical-align: middle; }
|
|
258
|
-
.note { color: var(--preview-muted); font-size: 13px; margin-top: 0.5rem; }
|
|
259
|
-
.empty { color: var(--preview-muted); font-style: italic; }${verifyStyle}
|
|
260
|
-
</style>
|
|
261
|
-
</head>
|
|
262
|
-
<body>
|
|
263
|
-
<h1>Design tokens</h1>
|
|
264
|
-
<p class="note">Generated from <code>canon/DESIGN.md</code> by <code>bun run web:previews</code>. Token preview only, not a screen mock.</p>${summary}
|
|
265
|
-
${sections.join('\n')}
|
|
266
|
-
</body>
|
|
267
|
-
</html>
|
|
268
|
-
`
|
|
243
|
+
return fillTemplate(readTemplate('preview.html'), {
|
|
244
|
+
favicon: favicon === undefined ? '' : `\n${faviconLink(favicon)}`,
|
|
245
|
+
chrome: previewChrome(),
|
|
246
|
+
bodyStack,
|
|
247
|
+
verifyStyle,
|
|
248
|
+
summary,
|
|
249
|
+
sections: sections.join('\n'),
|
|
250
|
+
})
|
|
269
251
|
}
|
|
270
252
|
|
|
271
253
|
function sectionPersonality(text: string): string {
|