@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.
Files changed (81) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/review-ui/references/post.md +7 -5
  3. package/docs/agents/canvas.md +8 -0
  4. package/docs/agents/pr-evidence.md +7 -7
  5. package/package.json +1 -1
  6. package/src/canvas/client/app.tsx +105 -4
  7. package/src/canvas/client/inspector.tsx +13 -4
  8. package/src/canvas/client/keys.ts +23 -6
  9. package/src/canvas/client/main.tsx +9 -0
  10. package/src/canvas/client/pages-panel.tsx +5 -1
  11. package/src/canvas/client/panel-handle.tsx +90 -0
  12. package/src/canvas/client/shell.css +86 -2
  13. package/src/canvas/client/state.ts +159 -6
  14. package/src/canvas/client/surface.tsx +37 -1
  15. package/src/canvas/client/tools.tsx +91 -2
  16. package/src/canvas/content.ts +65 -0
  17. package/src/canvas/edit.ts +195 -23
  18. package/src/canvas/history.ts +116 -0
  19. package/src/canvas/server.ts +126 -13
  20. package/src/{exec.ts → cli/exec.ts} +1 -1
  21. package/src/{help.ts → cli/help.ts} +1 -1
  22. package/src/cli.ts +1 -1
  23. package/src/commands/audits.ts +9 -1
  24. package/src/commands/autoship.ts +1 -1
  25. package/src/commands/canvas.ts +1 -1
  26. package/src/commands/capture.ts +1 -1
  27. package/src/commands/census.ts +1 -1
  28. package/src/commands/claude.ts +2 -2
  29. package/src/commands/comments.ts +1 -1
  30. package/src/commands/context.ts +1 -1
  31. package/src/commands/demo.ts +9 -1
  32. package/src/commands/deps.ts +1 -1
  33. package/src/commands/design.ts +9 -1
  34. package/src/commands/docs.ts +1 -1
  35. package/src/commands/driver.ts +9 -1
  36. package/src/commands/feedback.ts +1 -1
  37. package/src/commands/gate.ts +1 -1
  38. package/src/commands/gov.ts +1 -1
  39. package/src/commands/indexes.ts +1 -1
  40. package/src/commands/init.ts +10 -2
  41. package/src/commands/intake.ts +1 -1
  42. package/src/commands/inventory.ts +9 -1
  43. package/src/commands/labels.ts +1 -1
  44. package/src/commands/markdown.ts +1 -1
  45. package/src/commands/migrate.ts +1 -1
  46. package/src/commands/pr.ts +141 -2
  47. package/src/commands/records.ts +1 -1
  48. package/src/commands/repo.ts +1 -1
  49. package/src/commands/sandbox.ts +1 -1
  50. package/src/commands/secrets.ts +9 -1
  51. package/src/commands/sessions.ts +1 -1
  52. package/src/commands/slides.ts +1 -1
  53. package/src/commands/standards.ts +1 -1
  54. package/src/commands/sync.ts +2 -2
  55. package/src/commands/targets.ts +1 -1
  56. package/src/commands/tasks.ts +1 -1
  57. package/src/commands/teach.ts +1 -1
  58. package/src/commands/tooling.ts +1 -1
  59. package/src/commands/transcripts.ts +1 -1
  60. package/src/commands/upgrade.ts +1 -1
  61. package/src/commands/wiki.ts +1 -1
  62. package/src/commands/worktrees.ts +1 -1
  63. package/src/design/brand-favicon.ts +18 -0
  64. package/src/gate/measures.ts +1 -1
  65. package/src/init/run.ts +1 -1
  66. package/src/migrate/rename.ts +1 -1
  67. package/src/pr/evidence.ts +41 -7
  68. package/src/roots/project.ts +1 -1
  69. package/src/sandbox/headless/run.ts +1 -1
  70. package/src/sandbox/provision.ts +1 -1
  71. package/src/sandbox/scenario.ts +1 -1
  72. package/src/serve/report.ts +1 -1
  73. package/src/sync/engine.ts +1 -1
  74. package/src/sync/git.ts +1 -1
  75. package/src/sync/workflow.ts +1 -1
  76. package/src/targets/validate.ts +1 -1
  77. package/src/teach/nav.ts +5 -12
  78. package/src/tooling/inject.ts +1 -1
  79. package/src/tooling/verify-stack.ts +1 -1
  80. /package/src/{cli-run.ts → cli/run.ts} +0 -0
  81. /package/src/{ui.ts → cli/ui.ts} +0 -0
@@ -70,7 +70,7 @@ import {
70
70
  writeSettings,
71
71
  } from '@/claude/settings'
72
72
  import { copyPreservingMode } from '@/sync/copy'
73
- import { execScript } from '@/exec'
73
+ import { execScript } from '@/cli/exec'
74
74
  import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
75
75
  import { recordDir } from '@/roots/record'
76
76
  import { SURFACE_ENTRIES, surfaceDir } from '@/roots/surface'
@@ -90,7 +90,7 @@ import {
90
90
  pipeOutput,
91
91
  plural,
92
92
  select,
93
- } from '@/ui'
93
+ } from '@/cli/ui'
94
94
  import { describeSkew, readSkew, type SkewReport } from '@/version/skew'
95
95
 
96
96
  interface SeedsListOptions {
@@ -9,7 +9,7 @@ import {
9
9
  } from '@/comments/scan'
10
10
  import { type TrendPoint, trend } from '@/comments/trend'
11
11
  import { loadVocabulary, type Vocabulary } from '@/comments/vocabulary'
12
- import { intro, logInfo, logStep, logWarn, outro, pipeOutput } from '@/ui'
12
+ import { intro, logInfo, logStep, logWarn, outro, pipeOutput } from '@/cli/ui'
13
13
 
14
14
  interface ScanCommandOptions {
15
15
  readonly json?: boolean
@@ -80,7 +80,7 @@ import {
80
80
  outro,
81
81
  pipeOutput,
82
82
  plural,
83
- } from '@/ui'
83
+ } from '@/cli/ui'
84
84
 
85
85
  /** Returned when a gating finding is present. */
86
86
  const EXIT_GATE = 2
@@ -12,7 +12,15 @@ import {
12
12
  } from '@/demo/container'
13
13
  import { DEFAULT_CURSORS } from '@/demo/cursors'
14
14
  import { loadCursorTheme } from '@/demo/theme'
15
- import { intro, logError, logInfo, logStep, logWarn, outro, plural } from '@/ui'
15
+ import {
16
+ intro,
17
+ logError,
18
+ logInfo,
19
+ logStep,
20
+ logWarn,
21
+ outro,
22
+ plural,
23
+ } from '@/cli/ui'
16
24
 
17
25
  const DEFAULT_OUT = 'demos'
18
26
 
@@ -15,7 +15,7 @@ import {
15
15
  outro,
16
16
  pipeOutput,
17
17
  plural,
18
- } from '@/ui'
18
+ } from '@/cli/ui'
19
19
 
20
20
  interface AuditCommandOptions {
21
21
  readonly json?: boolean
@@ -16,7 +16,15 @@ import { creationRel, SCRATCH } from '@/roots/record'
16
16
  import { surfaceDir } from '@/roots/surface'
17
17
  import { recordStamp, runDomainSync } from '@/sync/engine'
18
18
  import { isDirectory, resolveTarget } from '@/targets/validate'
19
- import { intro, logAdd, logError, logInfo, logWarn, outro, palette } from '@/ui'
19
+ import {
20
+ intro,
21
+ logAdd,
22
+ logError,
23
+ logInfo,
24
+ logWarn,
25
+ outro,
26
+ palette,
27
+ } from '@/cli/ui'
20
28
 
21
29
  export function register(program: Command): void {
22
30
  const design = program
@@ -2,7 +2,7 @@ import type { Command } from 'commander'
2
2
  import { type DocEntry, listDocs } from '@/docs/list'
3
3
  import { listTopics, readTopic, resolveTopic } from '@/docs/read'
4
4
  import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
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
  export function register(program: Command): void {
8
8
  const docs = program
@@ -6,7 +6,15 @@ import type { DriverRefusal } from '@/driver/drive'
6
6
  import { describeViewport } from '@/driver/probes/viewport'
7
7
  import { PROBE_NAMES, readDriverPlan } from '@/driver/steps'
8
8
  import type { PlanRefusal } from '@/driver/steps'
9
- import { intro, logError, logInfo, logStep, logWarn, outro, plural } from '@/ui'
9
+ import {
10
+ intro,
11
+ logError,
12
+ logInfo,
13
+ logStep,
14
+ logWarn,
15
+ outro,
16
+ plural,
17
+ } from '@/cli/ui'
10
18
 
11
19
  /**
12
20
  * Holds wiring only. Every browser reference sits behind `loadDriver`, because
@@ -12,7 +12,7 @@ import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
12
12
  import { creationRel } from '@/roots/record'
13
13
  import { createGithubIssue } from '@/feedback/github'
14
14
  import { issueFailureMessage } from '@/feedback/github-format'
15
- import { frameError, frameSuccess, logWarn, palette } from '@/ui'
15
+ import { frameError, frameSuccess, logWarn, palette } from '@/cli/ui'
16
16
 
17
17
  function readStdin(): Promise<string> {
18
18
  return new Promise((resolveStream, rejectStream) => {
@@ -22,7 +22,7 @@ import {
22
22
  palette,
23
23
  pipeOutput,
24
24
  plural,
25
- } from '@/ui'
25
+ } from '@/cli/ui'
26
26
  import { repairBareFlag } from '@/worktrees/bare-flag'
27
27
 
28
28
  interface RunCommandOptions {
@@ -65,7 +65,7 @@ import {
65
65
  outro,
66
66
  palette,
67
67
  select,
68
- } from '@/ui'
68
+ } from '@/cli/ui'
69
69
 
70
70
  /**
71
71
  * Where the rules payload lands, resolved per target rather than once at import.
@@ -6,7 +6,7 @@ import { buildIndexCatalog } from '@/indexes/list'
6
6
  import { exitCodeFor, type RegenResult, regenOne } from '@/indexes/regen'
7
7
  import { gitEnv } from '@/git/env'
8
8
  import { findIndexedAncestor, isIgnored, listIndexes } from '@/indexes/walk'
9
- import { intro, logAdd, logInfo, logStep, logWarn, outro } from '@/ui'
9
+ import { intro, logAdd, logInfo, logStep, logWarn, outro } from '@/cli/ui'
10
10
 
11
11
  interface RegenCommandOptions {
12
12
  readonly dryRun?: boolean
@@ -1,12 +1,20 @@
1
1
  import type { Command } from 'commander'
2
- import { cliPath, cliRun } from '@/cli-run'
2
+ import { cliPath, cliRun } from '@/cli/run'
3
3
  import { PROJECT_ROOT } from '@/roots/project'
4
4
  import { applyInitOptions, flagsProvided } from '@/init/flags'
5
5
  import { type InitFlags, parseSkip, planInit } from '@/init/plan'
6
6
  import { runDomains } from '@/init/run'
7
7
  import { buildSteps } from '@/init/steps'
8
8
  import { resolveTarget } from '@/targets/validate'
9
- import { intro, logInfo, logStep, logWarn, outro, palette, select } from '@/ui'
9
+ import {
10
+ intro,
11
+ logInfo,
12
+ logStep,
13
+ logWarn,
14
+ outro,
15
+ palette,
16
+ select,
17
+ } from '@/cli/ui'
10
18
 
11
19
  interface InitOptions {
12
20
  /** Always present: the option falls back to `DEFAULT_STACK`. */
@@ -20,7 +20,7 @@ import {
20
20
  logWarn,
21
21
  outro,
22
22
  pipeOutput,
23
- } from '@/ui'
23
+ } from '@/cli/ui'
24
24
  import { mainWorktreeRoot } from '@/git/worktree'
25
25
 
26
26
  /**
@@ -9,7 +9,15 @@ import {
9
9
  import { abbreviate, groupByTreatment } from '@/inventory/group'
10
10
  import { findSubject, SUBJECTS } from '@/inventory/subjects'
11
11
  import type { WalkRefusal } from '@/inventory/walk'
12
- import { intro, logError, logInfo, logStep, logWarn, outro, plural } from '@/ui'
12
+ import {
13
+ intro,
14
+ logError,
15
+ logInfo,
16
+ logStep,
17
+ logWarn,
18
+ outro,
19
+ plural,
20
+ } from '@/cli/ui'
13
21
 
14
22
  /**
15
23
  * Holds wiring only. Every browser reference sits behind `loadWalker`, because
@@ -6,7 +6,7 @@ import { checkTitleFormat, type TitleFormatIssue } from '@/labels/format'
6
6
  import { MAP_REL } from '@/labels/map'
7
7
  import { scanPhaseLabels } from '@/labels/phase'
8
8
  import { scanTitleSpelling } from '@/labels/spelling'
9
- import { intro, logInfo, logStep, logWarn, outro, plural } from '@/ui'
9
+ import { intro, logInfo, logStep, logWarn, outro, plural } from '@/cli/ui'
10
10
 
11
11
  interface AuditOptions {
12
12
  readonly base?: string
@@ -29,7 +29,7 @@ import {
29
29
  outro,
30
30
  pipeOutput,
31
31
  plural,
32
- } from '@/ui'
32
+ } from '@/cli/ui'
33
33
 
34
34
  const EXIT_REFUSED = 1
35
35
  const EXIT_GATE = 2
@@ -46,7 +46,7 @@ import {
46
46
  } from '@/migrate/scratch-evidence'
47
47
  import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
48
48
  import { readStamp, stampedHashes } from '@/sync/stamp'
49
- import { logError, logInfo, logStep, logWarn, plural } from '@/ui'
49
+ import { logError, logInfo, logStep, logWarn, plural } from '@/cli/ui'
50
50
 
51
51
  interface SweepOptions {
52
52
  readonly json?: boolean
@@ -32,10 +32,14 @@ import {
32
32
  findEvidenceHead,
33
33
  findEvidenceLocal,
34
34
  findEvidencePreview,
35
+ type EvidenceItem,
36
+ type EvidenceReading,
35
37
  groupEvidence,
36
38
  hasMarkedEvidenceComment,
39
+ PNG_HEADER_BYTES,
37
40
  readChecklistBoxes,
38
41
  readOwed,
42
+ readPngWidth,
39
43
  renderEvidenceBody,
40
44
  settleChecklist,
41
45
  } from '@/pr/evidence'
@@ -87,7 +91,7 @@ import {
87
91
  selectBranchPull,
88
92
  } from '@/pr/rest'
89
93
  import { resolveReviewScope } from '@/pr/review-scope'
90
- import { intro, logInfo, logStep, logWarn, outro, plural } from '@/ui'
94
+ import { intro, logInfo, logStep, logWarn, outro, plural } from '@/cli/ui'
91
95
 
92
96
  const GH_TIMEOUT_MS = 30_000
93
97
 
@@ -1746,6 +1750,134 @@ async function readPullMergeBase(
1746
1750
  return sha === null || sha.trim() === '' ? undefined : sha.trim()
1747
1751
  }
1748
1752
 
1753
+ /** How many width reads run at once, since each downloads a whole image. */
1754
+ const WIDTH_READ_CONCURRENCY = 4
1755
+
1756
+ type WidthRead =
1757
+ | { readonly kind: 'read'; readonly width?: number }
1758
+ | { readonly kind: 'failed' }
1759
+
1760
+ /**
1761
+ * The pixel width of a PNG at `sha`, read over the contents API like every
1762
+ * other evidence read. The endpoint ignores a `Range` header, so the whole
1763
+ * file arrives and only its first bytes are read. A format other than PNG
1764
+ * reads without a width, and a refused or timed-out call reads as failed, so
1765
+ * the caller can keep the ceiling width and still report the cause.
1766
+ */
1767
+ async function readImageWidth(
1768
+ cwd: string,
1769
+ path: string,
1770
+ sha: string,
1771
+ ): Promise<WidthRead> {
1772
+ if (!/\.png$/i.test(path)) return { kind: 'read' }
1773
+ try {
1774
+ const result = await execa(
1775
+ 'gh',
1776
+ [
1777
+ 'api',
1778
+ '-H',
1779
+ 'Accept: application/vnd.github.raw',
1780
+ `repos/{owner}/{repo}/contents/${path.split('/').map(encodeURIComponent).join('/')}?ref=${sha}`,
1781
+ ],
1782
+ {
1783
+ cwd,
1784
+ timeout: GH_TIMEOUT_MS,
1785
+ env: gitEnv(),
1786
+ extendEnv: false,
1787
+ encoding: 'buffer',
1788
+ },
1789
+ )
1790
+ const width = readPngWidth(result.stdout.subarray(0, PNG_HEADER_BYTES))
1791
+ return width === undefined ? { kind: 'read' } : { kind: 'read', width }
1792
+ } catch {
1793
+ return { kind: 'failed' }
1794
+ }
1795
+ }
1796
+
1797
+ /** Runs `task` over `inputs` with at most `limit` in flight, keeping input order. */
1798
+ async function mapPool<T, R>(
1799
+ inputs: readonly T[],
1800
+ limit: number,
1801
+ task: (input: T) => Promise<R>,
1802
+ ): Promise<R[]> {
1803
+ const results: R[] = new Array(inputs.length)
1804
+ let next = 0
1805
+ const lane = async (): Promise<void> => {
1806
+ while (next < inputs.length) {
1807
+ const index = next++
1808
+ results[index] = await task(inputs[index] as T)
1809
+ }
1810
+ }
1811
+ await Promise.all(
1812
+ Array.from({ length: Math.min(limit, inputs.length) }, lane),
1813
+ )
1814
+ return results
1815
+ }
1816
+
1817
+ interface SizedEvidence {
1818
+ readonly reading: EvidenceReading
1819
+ /** Each image whose width read failed, as `head:<path>` or `base:<path>`, which rendered at the ceiling. */
1820
+ readonly unread: readonly string[]
1821
+ }
1822
+
1823
+ /** Each item with the pixel width of its head image and, when it has one, its base image. */
1824
+ async function withImageWidths(
1825
+ cwd: string,
1826
+ reading: EvidenceReading,
1827
+ base: string | undefined,
1828
+ head: string,
1829
+ ): Promise<SizedEvidence> {
1830
+ if (reading.kind === 'refused') return { reading, unread: [] }
1831
+ const jobs = reading.states.flatMap((entry, stateIndex) =>
1832
+ entry.items.flatMap((item, itemIndex) => [
1833
+ {
1834
+ stateIndex,
1835
+ itemIndex,
1836
+ side: 'head' as const,
1837
+ path: item.path,
1838
+ sha: head,
1839
+ },
1840
+ ...(item.added || base === undefined
1841
+ ? []
1842
+ : [
1843
+ {
1844
+ stateIndex,
1845
+ itemIndex,
1846
+ side: 'base' as const,
1847
+ path: item.path,
1848
+ sha: base,
1849
+ },
1850
+ ]),
1851
+ ]),
1852
+ )
1853
+ const reads = await mapPool(jobs, WIDTH_READ_CONCURRENCY, (job) =>
1854
+ readImageWidth(cwd, job.path, job.sha),
1855
+ )
1856
+ const unread: string[] = []
1857
+ const states = reading.states.map((entry, stateIndex) => ({
1858
+ ...entry,
1859
+ items: entry.items.map((item, itemIndex): EvidenceItem => {
1860
+ const widths: { width?: number; baseWidth?: number } = {}
1861
+ jobs.forEach((job, index) => {
1862
+ const read = reads[index]
1863
+ if (
1864
+ job.stateIndex !== stateIndex ||
1865
+ job.itemIndex !== itemIndex ||
1866
+ read === undefined
1867
+ ) {
1868
+ return
1869
+ }
1870
+ if (read.kind === 'failed') unread.push(`${job.side}:${job.path}`)
1871
+ else if (read.width !== undefined) {
1872
+ widths[job.side === 'head' ? 'width' : 'baseWidth'] = read.width
1873
+ }
1874
+ })
1875
+ return { ...item, ...widths }
1876
+ }),
1877
+ }))
1878
+ return { reading: { kind: 'read', states }, unread }
1879
+ }
1880
+
1749
1881
  async function runEvidence(
1750
1882
  number: string | undefined,
1751
1883
  opts: EvidenceOptions,
@@ -1954,7 +2086,8 @@ async function runEvidence(
1954
2086
  unsettled === undefined
1955
2087
  ? undefined
1956
2088
  : settleChecklist(unsettled, identity.head, findEvidenceHead(comments))
1957
- const states = grouped.kind === 'read' ? grouped.states : []
2089
+ const sized = await withImageWidths(root, grouped, base, identity.head)
2090
+ const states = sized.reading.kind === 'read' ? sized.reading.states : []
1958
2091
  const body = renderEvidenceBody(
1959
2092
  states,
1960
2093
  repo,
@@ -1993,6 +2126,11 @@ async function runEvidence(
1993
2126
  logInfo(
1994
2127
  `${plural(caseCount, 'case')} across ${plural(states.length, 'state')}`,
1995
2128
  )
2129
+ if (sized.unread.length > 0) {
2130
+ logWarn(
2131
+ `width unread for ${plural(sized.unread.length, 'image')}, rendered at 1000: ${sized.unread.join(', ')}`,
2132
+ )
2133
+ }
1996
2134
  if (preview !== undefined) logInfo(`preview ${preview}`)
1997
2135
  if (local !== undefined) logInfo(`local preview ${local}`)
1998
2136
  if (checklist !== undefined) {
@@ -2015,6 +2153,7 @@ async function runEvidence(
2015
2153
  head: identity.head,
2016
2154
  body,
2017
2155
  states,
2156
+ ...(sized.unread.length > 0 && { widthUnread: sized.unread }),
2018
2157
  ...(commentId !== undefined && { commentId }),
2019
2158
  ...carried,
2020
2159
  })}\n`,
@@ -55,7 +55,7 @@ import {
55
55
  outro,
56
56
  pipeOutput,
57
57
  plural,
58
- } from '@/ui'
58
+ } from '@/cli/ui'
59
59
  import { currentWorktreeRoot, mainWorktreeRoot } from '@/git/worktree'
60
60
 
61
61
  /** Returned when a record carries a finding, which is the gating result. */
@@ -17,7 +17,7 @@ import {
17
17
  logStep,
18
18
  logWarn,
19
19
  outro,
20
- } from '@/ui'
20
+ } from '@/cli/ui'
21
21
 
22
22
  const GH_TIMEOUT_MS = 30_000
23
23
 
@@ -40,7 +40,7 @@ import {
40
40
  outro,
41
41
  palette,
42
42
  select,
43
- } from '@/ui'
43
+ } from '@/cli/ui'
44
44
 
45
45
  const SANDBOX_DIR = join(PROJECT_ROOT, 'sandbox')
46
46
 
@@ -1,7 +1,15 @@
1
1
  import { resolve } from 'node:path'
2
2
  import type { Command } from 'commander'
3
3
  import { type ScanRefusal, scanShippedTree } from '@/secrets/scan'
4
- import { intro, logError, logInfo, logStep, logWarn, outro, plural } from '@/ui'
4
+ import {
5
+ intro,
6
+ logError,
7
+ logInfo,
8
+ logStep,
9
+ logWarn,
10
+ outro,
11
+ plural,
12
+ } from '@/cli/ui'
5
13
 
6
14
  interface ScanCommandOptions {
7
15
  readonly json?: boolean
@@ -23,7 +23,7 @@ import {
23
23
  outro,
24
24
  pipeOutput,
25
25
  plural,
26
- } from '@/ui'
26
+ } from '@/cli/ui'
27
27
  import { mainWorktreeRoot } from '@/git/worktree'
28
28
 
29
29
  interface ListCommandOptions {
@@ -4,7 +4,7 @@ import { INSTALL_BROWSER } from '@/browser/engine'
4
4
  import { creationRel, SCRATCH } from '@/roots/record'
5
5
  import { listDecks, resolveDeck, SLIDES_FOLDER } from '@/slides/decks'
6
6
  import { openDeck } from '@/slides/open'
7
- import { intro, outro, palette } from '@/ui'
7
+ import { intro, outro, palette } from '@/cli/ui'
8
8
 
9
9
  const SLIDES_AT = creationRel(process.cwd(), SLIDES_FOLDER)
10
10
 
@@ -17,7 +17,7 @@ import {
17
17
  outro,
18
18
  pipeOutput,
19
19
  plural,
20
- } from '@/ui'
20
+ } from '@/cli/ui'
21
21
 
22
22
  interface StandardsListOptions {
23
23
  readonly json?: boolean
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
3
  import type { Command } from 'commander'
4
- import { cliPath, cliRun } from '@/cli-run'
4
+ import { cliPath, cliRun } from '@/cli/run'
5
5
  import { PROJECT_ROOT } from '@/roots/project'
6
6
  import {
7
7
  buildCheckReport,
@@ -30,7 +30,7 @@ import {
30
30
  logWarn,
31
31
  outro,
32
32
  palette,
33
- } from '@/ui'
33
+ } from '@/cli/ui'
34
34
  import { describeSkew } from '@/version/skew'
35
35
 
36
36
  const SYNC_ARGS: Record<SyncDomain, readonly string[]> = {
@@ -17,7 +17,7 @@ import {
17
17
  outro,
18
18
  pipeOutput,
19
19
  plural,
20
- } from '@/ui'
20
+ } from '@/cli/ui'
21
21
 
22
22
  interface ListOptions {
23
23
  readonly json?: boolean
@@ -59,7 +59,7 @@ import {
59
59
  logWarn,
60
60
  outro,
61
61
  pipeOutput,
62
- } from '@/ui'
62
+ } from '@/cli/ui'
63
63
  import { mainWorktreeRoot } from '@/git/worktree'
64
64
 
65
65
  /** Returned when the board carries a finding, which is the gating result. */
@@ -42,7 +42,7 @@ import {
42
42
  logWarn,
43
43
  outro,
44
44
  pipeOutput,
45
- } from '@/ui'
45
+ } from '@/cli/ui'
46
46
  import { mainWorktreeRoot } from '@/git/worktree'
47
47
 
48
48
  /**
@@ -32,7 +32,7 @@ import {
32
32
  outro,
33
33
  palette,
34
34
  select,
35
- } from '@/ui'
35
+ } from '@/cli/ui'
36
36
 
37
37
  interface SyncOptions {
38
38
  readonly skip?: string
@@ -2,7 +2,7 @@ import { resolve } from 'node:path'
2
2
  import type { Command } from 'commander'
3
3
  import { recordDir } from '@/roots/record'
4
4
  import { ensureYtDlp, fetchOne } from '@/transcripts/fetch'
5
- import { palette } from '@/ui'
5
+ import { palette } from '@/cli/ui'
6
6
  import { mainWorktreeRoot } from '@/git/worktree'
7
7
 
8
8
  interface TranscriptOptions {
@@ -9,7 +9,7 @@ import {
9
9
  logWarn,
10
10
  outro,
11
11
  select,
12
- } from '@/ui'
12
+ } from '@/cli/ui'
13
13
  import { compareVersions, parseVersion } from '@/version/compare'
14
14
  import { readInstalled, UNKNOWN_LABEL } from '@/version/installed'
15
15
  import { detectManager, installCommand, type Manager } from '@/version/manager'
@@ -11,7 +11,7 @@ import {
11
11
  outro,
12
12
  palette,
13
13
  select,
14
- } from '@/ui'
14
+ } from '@/cli/ui'
15
15
  import {
16
16
  applyWikiInit,
17
17
  isWikiTarget,
@@ -8,7 +8,7 @@ import {
8
8
  outro,
9
9
  pipeOutput,
10
10
  plural,
11
- } from '@/ui'
11
+ } from '@/cli/ui'
12
12
  import {
13
13
  type Refusal,
14
14
  type ReclaimReport,
@@ -0,0 +1,18 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { FAVICON_COLORS, renderFavicon } from '@/design/favicon'
4
+ import { PROJECT_ROOT } from '@/roots/project'
5
+
6
+ const BRAND_MARK = 'assets/brand/mark.svg'
7
+
8
+ /**
9
+ * The brand icon, built from the mark and the favicon's own pair, the same two
10
+ * inputs `web/public/favicon.svg` is generated from. Every surface that shows
11
+ * the icon reads it here rather than keeping a copy that drifts from the mark.
12
+ * It sits apart from `favicon.ts` because the card build imports that module
13
+ * through an `@` alias pointed at `web/src`, so it must import nothing.
14
+ */
15
+ export function brandFavicon(): string {
16
+ const mark = readFileSync(join(PROJECT_ROOT, BRAND_MARK), 'utf8')
17
+ return renderFavicon(mark, FAVICON_COLORS)
18
+ }
@@ -39,7 +39,7 @@ import {
39
39
  } from '@/shipped/references'
40
40
  import { surfaceDir } from '@/roots/surface'
41
41
  import { seedRoots } from '@/tooling/seeds'
42
- import { plural } from '@/ui'
42
+ import { plural } from '@/cli/ui'
43
43
  import {
44
44
  README_PARAPHRASE_MARKER,
45
45
  readmeCitationsIn,
package/src/init/run.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { logInfo, logStep, logWarn } from '@/ui'
1
+ import { logInfo, logStep, logWarn } from '@/cli/ui'
2
2
 
3
3
  /**
4
4
  * A domain either runs or announces why it did not. Governance under
@@ -211,7 +211,7 @@ export const AITK_RULES: RenameRules = defineRenameRules({
211
211
  'src/sync/stamp.test.ts',
212
212
  'src/targets/registry.test.ts',
213
213
  'src/targets/sweep.test.ts',
214
- 'src/ui.test.ts',
214
+ 'src/cli/ui.test.ts',
215
215
  ],
216
216
  articleFixups: [{ pattern: AITK_ARTICLE, replacement: '$1$2$3' }],
217
217
  })