@erclx/canon 5.28.0 → 5.30.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/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/review-ui/references/post.md +7 -5
- package/docs/agents/canvas.md +8 -0
- package/docs/agents/pr-evidence.md +7 -7
- package/package.json +1 -1
- package/src/canvas/client/app.tsx +105 -4
- package/src/canvas/client/inspector.tsx +13 -4
- package/src/canvas/client/keys.ts +23 -6
- package/src/canvas/client/main.tsx +9 -0
- package/src/canvas/client/pages-panel.tsx +5 -1
- package/src/canvas/client/panel-handle.tsx +90 -0
- package/src/canvas/client/shell.css +86 -2
- package/src/canvas/client/state.ts +159 -6
- package/src/canvas/client/surface.tsx +37 -1
- package/src/canvas/client/tools.tsx +91 -2
- package/src/canvas/content.ts +65 -0
- package/src/canvas/edit.ts +195 -23
- package/src/canvas/history.ts +116 -0
- package/src/canvas/server.ts +126 -13
- package/src/{exec.ts → cli/exec.ts} +1 -1
- package/src/{help.ts → cli/help.ts} +1 -1
- package/src/cli.ts +1 -1
- package/src/commands/audits.ts +9 -1
- package/src/commands/autoship.ts +1 -1
- package/src/commands/canvas.ts +1 -1
- package/src/commands/capture.ts +1 -1
- package/src/commands/census.ts +1 -1
- package/src/commands/claude.ts +2 -2
- package/src/commands/comments.ts +1 -1
- package/src/commands/context.ts +1 -1
- package/src/commands/demo.ts +9 -1
- package/src/commands/deps.ts +1 -1
- package/src/commands/design.ts +9 -1
- package/src/commands/docs.ts +1 -1
- package/src/commands/driver.ts +9 -1
- package/src/commands/feedback.ts +1 -1
- package/src/commands/gate.ts +1 -1
- package/src/commands/gov.ts +1 -1
- package/src/commands/indexes.ts +1 -1
- package/src/commands/init.ts +10 -2
- package/src/commands/intake.ts +1 -1
- package/src/commands/inventory.ts +9 -1
- package/src/commands/labels.ts +1 -1
- package/src/commands/markdown.ts +1 -1
- package/src/commands/migrate.ts +1 -1
- package/src/commands/pr.ts +141 -2
- package/src/commands/records.ts +1 -1
- package/src/commands/repo.ts +1 -1
- package/src/commands/sandbox.ts +1 -1
- package/src/commands/secrets.ts +9 -1
- package/src/commands/sessions.ts +1 -1
- package/src/commands/slides.ts +1 -1
- package/src/commands/standards.ts +1 -1
- package/src/commands/sync.ts +2 -2
- package/src/commands/targets.ts +1 -1
- package/src/commands/tasks.ts +1 -1
- package/src/commands/teach.ts +1 -1
- package/src/commands/tooling.ts +1 -1
- package/src/commands/transcripts.ts +1 -1
- package/src/commands/upgrade.ts +1 -1
- package/src/commands/wiki.ts +1 -1
- package/src/commands/worktrees.ts +1 -1
- package/src/design/brand-favicon.ts +18 -0
- package/src/gate/measures.ts +1 -1
- package/src/init/run.ts +1 -1
- package/src/migrate/rename.ts +1 -1
- package/src/pr/evidence.ts +41 -7
- package/src/roots/project.ts +1 -1
- package/src/sandbox/headless/run.ts +1 -1
- package/src/sandbox/provision.ts +1 -1
- package/src/sandbox/scenario.ts +1 -1
- package/src/serve/report.ts +1 -1
- package/src/sync/engine.ts +1 -1
- package/src/sync/git.ts +1 -1
- package/src/sync/workflow.ts +1 -1
- package/src/targets/validate.ts +1 -1
- package/src/teach/nav.ts +5 -12
- package/src/tooling/inject.ts +1 -1
- package/src/tooling/verify-stack.ts +1 -1
- /package/src/{cli-run.ts → cli/run.ts} +0 -0
- /package/src/{ui.ts → cli/ui.ts} +0 -0
package/src/pr/evidence.ts
CHANGED
|
@@ -57,6 +57,10 @@ export interface EvidenceItem {
|
|
|
57
57
|
readonly stem: string
|
|
58
58
|
/** True when `path` did not exist at the comparison's base commit. */
|
|
59
59
|
readonly added: boolean
|
|
60
|
+
/** Pixel width of the head image, read from its PNG header. Absent when unread. */
|
|
61
|
+
readonly width?: number
|
|
62
|
+
/** Pixel width of the base image, absent for an added case or an unread header. */
|
|
63
|
+
readonly baseWidth?: number
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
export interface EvidenceState {
|
|
@@ -140,12 +144,33 @@ function rawUrl(repo: string, sha: string, path: string): string {
|
|
|
140
144
|
}
|
|
141
145
|
|
|
142
146
|
/**
|
|
143
|
-
* The
|
|
144
|
-
*
|
|
145
|
-
*
|
|
147
|
+
* The most pixels a screenshot asks for. GitHub sizes a comment table to its
|
|
148
|
+
* content and clamps an image to its cell, so a width past any comment fills
|
|
149
|
+
* the cell, where `width="100%"` leaves a narrow screenshot narrow. An image
|
|
150
|
+
* asks for the smaller of its own width and this ceiling, so a small crop
|
|
151
|
+
* shows at its true size and is never scaled up. An image whose width was not
|
|
152
|
+
* read asks for the ceiling.
|
|
146
153
|
*/
|
|
147
154
|
const IMAGE_WIDTH = 1000
|
|
148
155
|
|
|
156
|
+
const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]
|
|
157
|
+
|
|
158
|
+
/** Bytes of a PNG that reach the end of IHDR's width field. */
|
|
159
|
+
export const PNG_HEADER_BYTES = 24
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The pixel width in a PNG's IHDR chunk, or undefined for bytes that are not
|
|
163
|
+
* a PNG with a header or that carry a zero width.
|
|
164
|
+
*/
|
|
165
|
+
export function readPngWidth(bytes: Uint8Array): number | undefined {
|
|
166
|
+
if (bytes.length < PNG_HEADER_BYTES) return undefined
|
|
167
|
+
if (PNG_SIGNATURE.some((byte, index) => bytes[index] !== byte)) {
|
|
168
|
+
return undefined
|
|
169
|
+
}
|
|
170
|
+
const width = new DataView(bytes.buffer, bytes.byteOffset).getUint32(16)
|
|
171
|
+
return width > 0 ? width : undefined
|
|
172
|
+
}
|
|
173
|
+
|
|
149
174
|
/** A table cell and an attribute value share one escape, since a stem is a path segment placed in both. */
|
|
150
175
|
function escapeCell(text: string): string {
|
|
151
176
|
return text
|
|
@@ -156,8 +181,9 @@ function escapeCell(text: string): string {
|
|
|
156
181
|
.replaceAll('|', '|')
|
|
157
182
|
}
|
|
158
183
|
|
|
159
|
-
function evidenceImage(url: string, alt: string): string {
|
|
160
|
-
|
|
184
|
+
function evidenceImage(url: string, alt: string, width?: number): string {
|
|
185
|
+
const shown = Math.min(width ?? IMAGE_WIDTH, IMAGE_WIDTH)
|
|
186
|
+
return `<img src="${url}" width="${shown}" alt="${alt}">`
|
|
161
187
|
}
|
|
162
188
|
|
|
163
189
|
const EVIDENCE_IMAGE = String.raw`(?:!\[\]\([^)]*\)|<img [^>]*>)`
|
|
@@ -232,8 +258,16 @@ export function renderEvidenceBody(
|
|
|
232
258
|
const stem = escapeCell(item.stem)
|
|
233
259
|
const before = item.added
|
|
234
260
|
? '*(new)*'
|
|
235
|
-
: evidenceImage(
|
|
236
|
-
|
|
261
|
+
: evidenceImage(
|
|
262
|
+
rawUrl(repo, base, item.path),
|
|
263
|
+
`${stem} base`,
|
|
264
|
+
item.baseWidth,
|
|
265
|
+
)
|
|
266
|
+
const after = evidenceImage(
|
|
267
|
+
rawUrl(repo, head, item.path),
|
|
268
|
+
`${stem} head`,
|
|
269
|
+
item.width,
|
|
270
|
+
)
|
|
237
271
|
return hasBase
|
|
238
272
|
? `| ${stem} | ${before} | ${after} |`
|
|
239
273
|
: `| ${stem} | ${after} |`
|
package/src/roots/project.ts
CHANGED
|
@@ -72,7 +72,7 @@ export function findCheckoutMismatch(startDir: string): string | undefined {
|
|
|
72
72
|
* The warning line every verb that answers from `PROJECT_ROOT` emits when the
|
|
73
73
|
* caller's cwd sits inside a second checkout, or `undefined` when it does not.
|
|
74
74
|
*
|
|
75
|
-
* It returns the string rather than logging it so this module keeps no `@/ui`
|
|
75
|
+
* It returns the string rather than logging it so this module keeps no `@/cli/ui`
|
|
76
76
|
* dependency and the wording is unit-testable on its own. The wording names
|
|
77
77
|
* both roots and no subcommand: the four chokepoints that carry it stand under
|
|
78
78
|
* eight-plus verbs each, so a per-verb suffix would cost a parameter at every
|
package/src/sandbox/provision.ts
CHANGED
package/src/sandbox/scenario.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from 'node:fs'
|
|
12
12
|
import { createRequire } from 'node:module'
|
|
13
13
|
import { dirname, join, relative } from 'node:path'
|
|
14
|
-
import { logError, logInfo, logStep, logWarn, palette, select } from '@/ui'
|
|
14
|
+
import { logError, logInfo, logStep, logWarn, palette, select } from '@/cli/ui'
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* The TypeScript form of a sandbox scenario: `sandbox/<category>/<command>.ts`,
|
package/src/serve/report.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
type ServeOutcome,
|
|
5
5
|
type ServeStarted,
|
|
6
6
|
} from '@/serve/static'
|
|
7
|
-
import { intro, logError, logInfo, logStep, logWarn, outro } from '@/ui'
|
|
7
|
+
import { intro, logError, logInfo, logStep, logWarn, outro } from '@/cli/ui'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The relative path where it stays inside the working directory, and the
|
package/src/sync/engine.ts
CHANGED
package/src/sync/git.ts
CHANGED
package/src/sync/workflow.ts
CHANGED
package/src/targets/validate.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { statSync } from 'node:fs'
|
|
2
2
|
import { resolve } from 'node:path'
|
|
3
3
|
import { checkoutMismatchWarning } from '@/roots/project'
|
|
4
|
-
import { logError, logWarn, outro } from '@/ui'
|
|
4
|
+
import { logError, logWarn, outro } from '@/cli/ui'
|
|
5
5
|
|
|
6
6
|
export function isDirectory(path: string): boolean {
|
|
7
7
|
try {
|
package/src/teach/nav.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readdir, readFile, rm } from 'node:fs/promises'
|
|
3
3
|
import { join, posix, relative } from 'node:path'
|
|
4
4
|
import {
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
TEACH_STYLESHEET_COMPONENTS,
|
|
7
7
|
} from '@/design/components'
|
|
8
8
|
import { buildDesignCss } from '@/design/css'
|
|
9
|
-
import {
|
|
9
|
+
import { brandFavicon } from '@/design/brand-favicon'
|
|
10
|
+
import { faviconLink } from '@/design/favicon'
|
|
10
11
|
import { stripFrontmatter } from '@/markdown/frontmatter'
|
|
11
12
|
import { parseFrontmatter, readField } from '@/indexes/frontmatter'
|
|
12
|
-
import { PROJECT_ROOT } from '@/roots/project'
|
|
13
13
|
import { compileScript } from '@/teach/browser/compile'
|
|
14
14
|
import { dismiss } from '@/teach/browser/dismiss'
|
|
15
15
|
import { focusLine } from '@/teach/browser/focus-line'
|
|
@@ -39,8 +39,6 @@ import {
|
|
|
39
39
|
} from '@/teach/workspace'
|
|
40
40
|
import { writeIfChanged } from '@/teach/write-if-changed'
|
|
41
41
|
|
|
42
|
-
const BRAND_MARK = 'assets/brand/mark.svg'
|
|
43
|
-
|
|
44
42
|
/**
|
|
45
43
|
* The link `teach-workspace` once had a session write by hand into every page.
|
|
46
44
|
* A lesson keeps whatever sits outside its marked regions, so the old icon
|
|
@@ -68,14 +66,9 @@ function dropHandWrittenHead(html: string): string {
|
|
|
68
66
|
)
|
|
69
67
|
}
|
|
70
68
|
|
|
71
|
-
/**
|
|
72
|
-
* Built from the brand mark and the favicon's own pair, the same two inputs
|
|
73
|
-
* `web/public/favicon.svg` is generated from, so every teach page carries the
|
|
74
|
-
* icon the landing page does rather than a copy of its own.
|
|
75
|
-
*/
|
|
69
|
+
/** Every teach page carries the icon the landing page does rather than a copy of its own. */
|
|
76
70
|
function teachFavicon(): string {
|
|
77
|
-
|
|
78
|
-
return faviconLink(renderFavicon(mark, FAVICON_COLORS))
|
|
71
|
+
return faviconLink(brandFavicon())
|
|
79
72
|
}
|
|
80
73
|
|
|
81
74
|
/**
|
package/src/tooling/inject.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
SUBFOLDER_SPELL_CONFIG,
|
|
28
28
|
subfolderSpellConfig,
|
|
29
29
|
} from '@/tooling/subfolder'
|
|
30
|
-
import { logAdd, logRemove, logStep, logWarn } from '@/ui'
|
|
30
|
+
import { logAdd, logRemove, logStep, logWarn } from '@/cli/ui'
|
|
31
31
|
|
|
32
32
|
export async function injectConfigs(
|
|
33
33
|
chain: readonly Manifest[],
|
|
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs'
|
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import { execa, type Options } from 'execa'
|
|
4
4
|
import { isStackExcluded, loadManifest } from '@/tooling/manifest'
|
|
5
|
-
import { intro, logError, logInfo, logStep, logWarn, outro } from '@/ui'
|
|
5
|
+
import { intro, logError, logInfo, logStep, logWarn, outro } from '@/cli/ui'
|
|
6
6
|
|
|
7
7
|
const SCREENSHOT_TIMEOUT_MS = 60_000
|
|
8
8
|
const TAIL_LINES = 20
|
|
File without changes
|
/package/src/{ui.ts → cli/ui.ts}
RENAMED
|
File without changes
|