@erclx/canon 5.26.0 → 5.28.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/README.md +2 -2
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/SKILL.md +2 -0
- package/claude/skills/auto-ship/SKILL.md +3 -11
- package/claude/skills/auto-ship/references/review-findings.md +20 -0
- package/claude/skills/backlog-triage/SKILL.md +2 -0
- package/claude/skills/bash-cli-script/SKILL.md +2 -0
- package/claude/skills/build-in-slices/SKILL.md +2 -0
- package/claude/skills/canon-cli/SKILL.md +2 -0
- package/claude/skills/canon-feedback/SKILL.md +2 -0
- package/claude/skills/canon-feedback-triage/SKILL.md +2 -0
- package/claude/skills/canon-operator/SKILL.md +2 -0
- package/claude/skills/canon-rollout/SKILL.md +3 -11
- package/claude/skills/canon-rollout/references/failure-recovery.md +20 -0
- package/claude/skills/canvas/SKILL.md +13 -0
- package/claude/skills/ci-workflow/SKILL.md +2 -0
- package/claude/skills/code-craft/SKILL.md +2 -0
- package/claude/skills/codebase-layout/SKILL.md +2 -0
- package/claude/skills/context-fold/SKILL.md +3 -17
- package/claude/skills/context-fold/references/completion-report.md +26 -0
- package/claude/skills/create-rule/SKILL.md +2 -0
- package/claude/skills/create-skill/SKILL.md +2 -0
- package/claude/skills/create-standard/SKILL.md +2 -0
- package/claude/skills/decision-escalate/SKILL.md +2 -0
- package/claude/skills/deploy-app/SKILL.md +2 -0
- package/claude/skills/deprecation-migration/SKILL.md +2 -0
- package/claude/skills/design-extract/SKILL.md +2 -0
- package/claude/skills/design-taste/SKILL.md +2 -0
- package/claude/skills/docs-sync/SKILL.md +2 -0
- package/claude/skills/document-health/SKILL.md +2 -0
- package/claude/skills/draft-and-pick/SKILL.md +2 -0
- package/claude/skills/draft-doc/SKILL.md +2 -0
- package/claude/skills/draft-doc/references/wiki.md +1 -1
- package/claude/skills/draft-figure/SKILL.md +2 -0
- package/claude/skills/draft-identity/SKILL.md +2 -0
- package/claude/skills/draft-ready/SKILL.md +5 -1
- package/claude/skills/draft-screencast/SKILL.md +2 -0
- package/claude/skills/draft-slides/SKILL.md +2 -0
- package/claude/skills/git-branch/SKILL.md +2 -0
- package/claude/skills/git-commit/SKILL.md +2 -0
- package/claude/skills/git-followup/SKILL.md +2 -0
- package/claude/skills/git-issue/SKILL.md +2 -0
- package/claude/skills/git-pr/SKILL.md +2 -0
- package/claude/skills/git-pr/references/evidence.md +7 -7
- package/claude/skills/git-ship/SKILL.md +2 -0
- package/claude/skills/git-split/SKILL.md +2 -0
- package/claude/skills/git-stage/SKILL.md +2 -0
- package/claude/skills/git-worktree/SKILL.md +2 -0
- package/claude/skills/index-lookup/SKILL.md +2 -0
- package/claude/skills/markdown-propose/SKILL.md +2 -0
- package/claude/skills/memory-capture/SKILL.md +2 -0
- package/claude/skills/memory-review/SKILL.md +2 -0
- package/claude/skills/plan-feature/SKILL.md +2 -0
- package/claude/skills/plan-groundwork/SKILL.md +2 -0
- package/claude/skills/plan-intake/SKILL.md +2 -0
- package/claude/skills/plan-intake-answer/SKILL.md +2 -0
- package/claude/skills/project-commands/SKILL.md +2 -0
- package/claude/skills/read-frames/SKILL.md +2 -0
- package/claude/skills/record-screencast/SKILL.md +2 -0
- package/claude/skills/repo-metadata/SKILL.md +2 -0
- package/claude/skills/restate-plainly/SKILL.md +2 -0
- package/claude/skills/review-address/REQUIREMENT.md +4 -2
- package/claude/skills/review-address/SKILL.md +19 -14
- package/claude/skills/review-address/references/post-review-findings.md +16 -0
- package/claude/skills/review-address/references/rebase-conflicts.md +4 -2
- package/claude/skills/review-branch/SKILL.md +2 -0
- package/claude/skills/review-craft/SKILL.md +2 -0
- package/claude/skills/review-craft/references/ui.md +6 -6
- package/claude/skills/review-pr/SKILL.md +4 -8
- package/claude/skills/review-pr/references/body-variants.md +52 -0
- package/claude/skills/review-pr/references/key-changes.md +16 -0
- package/claude/skills/review-pr/references/post.md +15 -43
- package/claude/skills/review-ui/REQUIREMENT.md +2 -2
- package/claude/skills/review-ui/SKILL.md +16 -12
- package/claude/skills/review-ui/references/driving.md +8 -8
- package/claude/skills/review-ui/references/post.md +13 -3
- package/claude/skills/review-ui/references/tick.md +22 -0
- package/claude/skills/role-orchestrator/REQUIREMENT.md +15 -15
- package/claude/skills/role-orchestrator/SKILL.md +31 -42
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +18 -16
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +13 -3
- package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +23 -0
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +6 -34
- package/claude/skills/role-orchestrator/references/orchestrator-output.md +4 -4
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +5 -5
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +12 -23
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +6 -6
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +38 -0
- package/claude/skills/role-orchestrator/references/orchestrator-sweep.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-watch.md +26 -0
- package/claude/skills/role-orchestrator/scripts/poll.ts +304 -10
- package/claude/skills/role-planner/REQUIREMENT.md +1 -1
- package/claude/skills/role-planner/SKILL.md +4 -2
- package/claude/skills/role-reviewer/REQUIREMENT.md +1 -1
- package/claude/skills/role-reviewer/SKILL.md +3 -1
- package/claude/skills/role-worker/SKILL.md +5 -2
- package/claude/skills/search-craft/SKILL.md +2 -0
- package/claude/skills/seed-sync/SKILL.md +2 -0
- package/claude/skills/session-compact/SKILL.md +2 -0
- package/claude/skills/session-map/SKILL.md +2 -0
- package/claude/skills/session-relay/SKILL.md +2 -0
- package/claude/skills/session-resume/SKILL.md +2 -0
- package/claude/skills/session-worktree/SKILL.md +3 -11
- package/claude/skills/session-worktree/references/install-command.md +20 -0
- package/claude/skills/sketch-design/SKILL.md +2 -0
- package/claude/skills/systematic-debugging/SKILL.md +2 -0
- package/claude/skills/target-check/SKILL.md +2 -0
- package/claude/skills/target-setup/SKILL.md +2 -0
- package/claude/skills/task-board/SKILL.md +2 -0
- package/claude/skills/teach-workspace/SKILL.md +5 -15
- package/claude/skills/teach-workspace/references/lesson-build.md +75 -0
- package/claude/skills/teach-workspace/references/lesson-craft.md +13 -69
- package/claude/skills/teach-workspace/references/revisit.md +22 -0
- package/claude/skills/test-craft/SKILL.md +2 -0
- package/claude/skills/test-first/SKILL.md +2 -0
- package/claude/skills/ui-checklist/SKILL.md +3 -1
- package/claude/skills/ux-audit/SKILL.md +2 -0
- package/claude/skills/ux-measure/SKILL.md +2 -0
- package/claude/skills/ux-walkthrough/SKILL.md +2 -0
- package/claude/skills/video-craft/SKILL.md +2 -0
- package/claude/skills/write-human/SKILL.md +2 -0
- package/claude/skills/youtube-transcripts/SKILL.md +2 -0
- package/docs/agents/canvas.md +24 -8
- package/docs/agents/capture.md +5 -5
- package/docs/agents/commands.md +2 -2
- package/docs/agents/gate.md +3 -1
- package/docs/agents/index.md +5 -4
- package/docs/agents/install-and-sync.md +7 -0
- package/docs/agents/key-changes.md +1 -1
- package/docs/agents/pr-evidence.md +47 -23
- package/docs/agents/pr-local.md +19 -13
- package/docs/agents/pr-preview.md +44 -21
- package/docs/agents/pr-tick.md +72 -0
- package/docs/agents/sandbox.md +30 -6
- package/docs/agents/scripting.md +8 -1
- package/docs/agents/skills-audit.md +2 -1
- package/docs/agents/tasks-plans.md +1 -1
- package/docs/agents/tasks-validate.md +18 -18
- package/docs/agents/tasks.md +1 -1
- package/docs/workflow/operating-model.md +15 -21
- package/docs/workflow/parallel-features.md +24 -3
- package/docs/workflow/skill-map.md +3 -1
- package/docs/workflow/zshrc-aliases.md +1 -1
- package/package.json +1 -1
- package/scripts/core/regen-hero.ts +1 -1
- package/scripts/lib/sandbox-path.sh +6 -162
- package/src/audits/baseline.ts +1 -1
- package/src/audits/run.ts +2 -2
- package/src/canvas/address.ts +12 -3
- package/src/canvas/capture.ts +1 -1
- package/src/canvas/client/inspector/align-grid.tsx +162 -0
- package/src/canvas/client/inspector/color-field.tsx +201 -171
- package/src/canvas/client/inspector/color-inputs.tsx +139 -0
- package/src/canvas/client/inspector/color-picker.tsx +668 -0
- package/src/canvas/client/inspector/color-space.ts +240 -0
- package/src/canvas/client/inspector/field.tsx +66 -3
- package/src/canvas/client/inspector/fields.css +473 -6
- package/src/canvas/client/inspector/size-field.tsx +69 -0
- package/src/canvas/client/inspector/token-select.tsx +109 -0
- package/src/canvas/client/inspector/values.ts +117 -3
- package/src/canvas/client/inspector.tsx +311 -15
- package/src/canvas/client/keys.ts +54 -0
- package/src/canvas/client/selection.tsx +199 -0
- package/src/canvas/client/shell.css +210 -12
- package/src/canvas/client/state.ts +165 -14
- package/src/canvas/client/surface.tsx +341 -39
- package/src/canvas/client/tools.tsx +64 -0
- package/src/canvas/content.ts +51 -2
- package/src/canvas/edit.ts +49 -6
- package/src/canvas/editing.ts +203 -0
- package/src/canvas/global.d.ts +15 -0
- package/src/canvas/server.ts +149 -12
- package/src/canvas/tokens.ts +60 -18
- package/src/census/count.ts +1 -1
- package/src/claude/seeds.ts +3 -3
- package/src/claude/skills-audit.ts +28 -0
- package/src/claude/skills-drift.ts +1 -1
- package/src/claude/skills-families.ts +36 -0
- package/src/claude/skills-list.ts +76 -15
- package/src/claude/skills-reach.ts +1 -1
- package/src/commands/audits.ts +2 -2
- package/src/commands/canvas.ts +180 -3
- package/src/commands/claude.ts +70 -12
- package/src/commands/design.ts +145 -5
- package/src/commands/docs.ts +1 -1
- package/src/commands/feedback.ts +2 -2
- package/src/commands/gate.ts +9 -2
- package/src/commands/gov.ts +3 -3
- package/src/commands/hooks.ts +1 -1
- package/src/commands/indexes.ts +1 -1
- package/src/commands/init.ts +2 -2
- package/src/commands/intake.ts +1 -1
- package/src/commands/migrate.ts +2 -2
- package/src/commands/pr.ts +288 -65
- package/src/commands/records.ts +1 -1
- package/src/commands/repo.ts +1 -1
- package/src/commands/sandbox.ts +64 -15
- package/src/commands/sessions.ts +1 -1
- package/src/commands/slides.ts +1 -1
- package/src/commands/sync.ts +2 -2
- package/src/commands/tasks.ts +21 -4
- package/src/commands/teach.ts +1 -1
- package/src/commands/tooling.ts +1 -1
- package/src/commands/transcripts.ts +2 -2
- package/src/commands/upgrade.ts +1 -1
- package/src/commands/wiki.ts +1 -1
- package/src/commands/worktrees.ts +1 -1
- package/src/comments/scan.ts +1 -1
- package/src/comments/trend.ts +1 -1
- package/src/context/architecture.ts +1 -1
- package/src/context/citations.ts +3 -3
- package/src/context/classify/extract.ts +3 -3
- package/src/context/folders.ts +2 -2
- package/src/context/requirements.ts +1 -1
- package/src/design/project-fonts.ts +392 -0
- package/src/docs/read.ts +1 -1
- package/src/exec.ts +1 -1
- package/src/feedback/github.ts +2 -2
- package/src/gate/measures.ts +48 -10
- package/src/gate/sequencer.ts +56 -4
- package/src/gate/stages.ts +17 -0
- package/src/gate/suite-lock.ts +214 -0
- package/src/{git-files.ts → git/files.ts} +2 -2
- package/src/{git-ignore.ts → git/ignore.ts} +1 -1
- package/src/{worktree.ts → git/worktree.ts} +1 -1
- package/src/gov/citations.ts +1 -1
- package/src/gov/install.ts +1 -1
- package/src/gov/superseded.ts +1 -1
- package/src/gov/test-order.ts +2 -2
- package/src/help.ts +1 -1
- package/src/hooks/post-merge.ts +1 -1
- package/src/indexes/walk.ts +1 -1
- package/src/intake/folder.ts +1 -1
- package/src/labels/audit.ts +1 -1
- package/src/labels/map.ts +1 -1
- package/src/labels/phase.ts +3 -3
- package/src/markdown/files.ts +1 -1
- package/src/migrate/apply.ts +1 -1
- package/src/migrate/record-layout.ts +1 -1
- package/src/migrate/records.ts +11 -11
- package/src/migrate/scratch-evidence.ts +1 -1
- package/src/migrate/surface-roots.ts +6 -6
- package/src/pr/bijection.ts +1 -1
- package/src/pr/evidence.ts +250 -59
- package/src/pr/local.ts +16 -10
- package/src/pr/preview.ts +78 -2
- package/src/pr/tick.ts +56 -0
- package/src/process/harness.ts +1 -1
- package/src/records/backup.ts +2 -2
- package/src/records/ordinal.ts +1 -1
- package/src/records/prune.ts +1 -1
- package/src/records/size.ts +1 -1
- package/src/records/stale-canonical.ts +2 -2
- package/src/records/stale.ts +1 -1
- package/src/records/validate.ts +2 -2
- package/src/{project-root.ts → roots/project.ts} +1 -1
- package/src/{record-root.ts → roots/record.ts} +1 -1
- package/src/sandbox/coverage.ts +11 -12
- package/src/sandbox/equivalence.ts +57 -26
- package/src/sandbox/headless/dispatch.ts +264 -0
- package/src/sandbox/headless/run.ts +653 -0
- package/src/sandbox/headless/watch.ts +145 -0
- package/src/sandbox/hooks.ts +1 -1
- package/src/sandbox/provision.ts +90 -29
- package/src/sandbox/scenario.ts +588 -0
- package/src/sandbox/tree.ts +4 -8
- package/src/secrets/scan.ts +1 -1
- package/src/sessions/claim.ts +1 -1
- package/src/sessions/live.ts +4 -1
- package/src/sessions/resolve.ts +1 -1
- package/src/sessions/transfer/bundle.ts +2 -2
- package/src/slides/decks.ts +1 -1
- package/src/slides/package/fonts.ts +6 -2
- package/src/standards/audit.ts +2 -2
- package/src/standards/read.ts +1 -1
- package/src/sync/check.ts +2 -2
- package/src/sync/engine.ts +3 -3
- package/src/sync/git.ts +1 -1
- package/src/sync/history.ts +1 -1
- package/src/sync/layout.ts +2 -2
- package/src/sync/reverse.ts +1 -1
- package/src/sync/stamp.ts +1 -1
- package/src/sync/target.ts +1 -1
- package/src/sync/workflow.ts +1 -1
- package/src/targets/pulls.ts +2 -2
- package/src/targets/sweep.ts +2 -2
- package/src/{target.ts → targets/validate.ts} +1 -1
- package/src/tasks/answers.ts +1 -1
- package/src/tasks/archive.ts +1 -1
- package/src/tasks/label.ts +1 -1
- package/src/tasks/reach.ts +2 -2
- package/src/tasks/trunk.ts +1 -1
- package/src/tasks/validate.ts +38 -14
- package/src/teach/nav.ts +1 -1
- package/src/teach/workspace.ts +1 -1
- package/src/tooling/inject.ts +2 -2
- package/src/tooling/read.ts +1 -1
- package/src/tooling/scan.ts +1 -1
- package/src/tooling/subfolder.ts +1 -1
- package/src/version/installed.ts +1 -1
- package/src/version/skew.ts +1 -1
- package/src/web/readme-citations.ts +1 -1
- package/src/worktrees/bare-flag.ts +1 -1
- package/src/worktrees/reclaim.ts +2 -2
- package/src/worktrees/remove.ts +1 -1
- package/standards/board.md +3 -3
- package/standards/canvas.md +2 -1
- package/standards/plan.md +13 -7
- package/standards/skill.md +1 -0
- package/standards/wiki.md +12 -3
- package/tooling/base/configs/.github/workflows/pr-local-preview.yml +2 -2
- package/tsconfig.json +1 -1
- package/scripts/lib/sandbox-dispatch.sh +0 -190
- /package/src/{client-commands.ts → gate/client-commands.ts} +0 -0
- /package/src/{git-base.ts → git/base.ts} +0 -0
- /package/src/{git-env.ts → git/env.ts} +0 -0
- /package/src/{legacy-path.ts → roots/legacy.ts} +0 -0
- /package/src/{surface-root.ts → roots/surface.ts} +0 -0
- /package/src/{copy.ts → sync/copy.ts} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { computed, signal } from '@preact/signals'
|
|
2
2
|
import type { ElementAddress } from '@/canvas/address'
|
|
3
|
-
import type { Frame, Page } from '@/canvas/content'
|
|
3
|
+
import type { Box, Frame, Page } from '@/canvas/content'
|
|
4
4
|
import type { TokenGroup } from '@/canvas/tokens'
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -22,6 +22,11 @@ export interface TokenSource {
|
|
|
22
22
|
|
|
23
23
|
export type LeftTab = 'pages' | 'theme'
|
|
24
24
|
|
|
25
|
+
export type Tool = 'move' | 'pan'
|
|
26
|
+
|
|
27
|
+
/** A Space pan held, and whether a drag has moved the view while it was. */
|
|
28
|
+
export type SpacePan = 'held' | 'dragged'
|
|
29
|
+
|
|
25
30
|
/** One property change to the selected element, as the inspector posts it. */
|
|
26
31
|
export interface PendingEdit {
|
|
27
32
|
readonly key: string
|
|
@@ -45,11 +50,19 @@ export interface SelectionRef extends FrameRef {
|
|
|
45
50
|
readonly element?: ElementRef
|
|
46
51
|
}
|
|
47
52
|
|
|
53
|
+
/** A frame a session says it is editing, until the stamp it expires at. */
|
|
54
|
+
export interface EditingRef extends FrameRef {
|
|
55
|
+
readonly by: string
|
|
56
|
+
readonly until: string
|
|
57
|
+
}
|
|
58
|
+
|
|
48
59
|
export interface PagesRecord {
|
|
49
60
|
readonly pages: readonly Page[]
|
|
50
61
|
readonly tokens: TokenSource
|
|
51
62
|
/** Absent when nothing is selected or the selected frame is gone. */
|
|
52
63
|
readonly selection?: SelectionRef | null
|
|
64
|
+
/** The live marks, which a server older than the mark leaves out. */
|
|
65
|
+
readonly editing?: readonly EditingRef[]
|
|
53
66
|
}
|
|
54
67
|
|
|
55
68
|
export interface ChangeEvent {
|
|
@@ -101,6 +114,9 @@ export const hoveredElement = signal<
|
|
|
101
114
|
{ readonly key: string; readonly index: number } | undefined
|
|
102
115
|
>(undefined)
|
|
103
116
|
|
|
117
|
+
/** Each marked frame's mark by frame key, dropped at its `until`. */
|
|
118
|
+
export const editingFrames = signal<ReadonlyMap<string, EditingRef>>(new Map())
|
|
119
|
+
|
|
104
120
|
/** Frame keys whose layers are open in the pages panel. */
|
|
105
121
|
export const expandedFrames = signal<ReadonlySet<string>>(new Set())
|
|
106
122
|
|
|
@@ -112,6 +128,16 @@ export const writeError = signal<string | undefined>(undefined)
|
|
|
112
128
|
|
|
113
129
|
export const leftTab = signal<LeftTab>('pages')
|
|
114
130
|
|
|
131
|
+
/** The tool the strip has lit, which a Space pan overrides while held. */
|
|
132
|
+
export const activeTool = signal<Tool>('move')
|
|
133
|
+
|
|
134
|
+
export const spacePan = signal<SpacePan | undefined>(undefined)
|
|
135
|
+
|
|
136
|
+
/** The tool in effect, so Space shows Pan lit and releasing it restores it. */
|
|
137
|
+
export const shownTool = computed<Tool>(() =>
|
|
138
|
+
spacePan.value ? 'pan' : activeTool.value,
|
|
139
|
+
)
|
|
140
|
+
|
|
115
141
|
/** The edit in flight, which holds its field until the server answers. */
|
|
116
142
|
export const pendingEdit = signal<PendingEdit | undefined>(undefined)
|
|
117
143
|
|
|
@@ -193,10 +219,62 @@ export function toggleLayers(key: string): void {
|
|
|
193
219
|
expandedFrames.value = next
|
|
194
220
|
}
|
|
195
221
|
|
|
222
|
+
/*
|
|
223
|
+
* An expiry writes no file and raises no change event, so nothing rereads the
|
|
224
|
+
* page list when a mark lapses. One timer, set for the earliest `until`, drops
|
|
225
|
+
* it instead, and every record resets it.
|
|
226
|
+
*/
|
|
227
|
+
let editingTimer: ReturnType<typeof setTimeout> | undefined
|
|
228
|
+
|
|
229
|
+
/*
|
|
230
|
+
* The longest delay a timer holds. A longer one fires at once, so an `until`
|
|
231
|
+
* far out would reschedule on every tick rather than wait.
|
|
232
|
+
*/
|
|
233
|
+
const MAX_TIMER_MS = 2 ** 31 - 1
|
|
234
|
+
|
|
235
|
+
function liveMarks(
|
|
236
|
+
marks: Iterable<readonly [string, EditingRef]>,
|
|
237
|
+
now: number,
|
|
238
|
+
): ReadonlyMap<string, EditingRef> {
|
|
239
|
+
return new Map([...marks].filter(([, mark]) => Date.parse(mark.until) > now))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function scheduleExpiry(): void {
|
|
243
|
+
clearTimeout(editingTimer)
|
|
244
|
+
editingTimer = undefined
|
|
245
|
+
const next = Math.min(
|
|
246
|
+
...[...editingFrames.value.values()].map((mark) => Date.parse(mark.until)),
|
|
247
|
+
)
|
|
248
|
+
if (!Number.isFinite(next)) return
|
|
249
|
+
editingTimer = setTimeout(
|
|
250
|
+
() => {
|
|
251
|
+
editingFrames.value = liveMarks(editingFrames.value, Date.now())
|
|
252
|
+
scheduleExpiry()
|
|
253
|
+
},
|
|
254
|
+
Math.min(MAX_TIMER_MS, Math.max(0, next - Date.now())),
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Keys each mark by the file of the frame it names, as the surface reads. */
|
|
259
|
+
function applyEditing(
|
|
260
|
+
all: readonly Page[],
|
|
261
|
+
marks: readonly EditingRef[],
|
|
262
|
+
): void {
|
|
263
|
+
const keyed = marks.flatMap((mark) => {
|
|
264
|
+
const frame = all
|
|
265
|
+
.find((candidate) => candidate.name === mark.page)
|
|
266
|
+
?.frames.find((candidate) => candidate.name === mark.frame)
|
|
267
|
+
return frame ? [[frameKey(mark.page, frame), mark] as const] : []
|
|
268
|
+
})
|
|
269
|
+
editingFrames.value = liveMarks(keyed, Date.now())
|
|
270
|
+
scheduleExpiry()
|
|
271
|
+
}
|
|
272
|
+
|
|
196
273
|
export function applyRecord(record: PagesRecord): void {
|
|
197
274
|
pages.value = record.pages
|
|
198
275
|
tokens.value = record.tokens
|
|
199
276
|
selection.value = record.selection ?? undefined
|
|
277
|
+
applyEditing(record.pages, record.editing ?? [])
|
|
200
278
|
loadError.value = undefined
|
|
201
279
|
isLoaded.value = true
|
|
202
280
|
}
|
|
@@ -320,7 +398,8 @@ const EDIT_NOTICES: Readonly<Record<string, string | undefined>> = {
|
|
|
320
398
|
/**
|
|
321
399
|
* Posts one property change to the element the address names. A refusal
|
|
322
400
|
* says why and reloads the frame, so a pending value never sits over a file
|
|
323
|
-
* that moved under it.
|
|
401
|
+
* that moved under it. Resolves to what the write answered, or undefined when
|
|
402
|
+
* nothing was written.
|
|
324
403
|
*/
|
|
325
404
|
export async function editElement(
|
|
326
405
|
ref: FrameRef,
|
|
@@ -329,7 +408,7 @@ export async function editElement(
|
|
|
329
408
|
property: string,
|
|
330
409
|
value: string,
|
|
331
410
|
fetchImpl: typeof fetch = fetch,
|
|
332
|
-
): Promise<
|
|
411
|
+
): Promise<{ readonly hash?: string } | undefined> {
|
|
333
412
|
const hash = editedHashes.value.get(key) ?? address.hash
|
|
334
413
|
pendingEdit.value = { key, index: address.index, property }
|
|
335
414
|
try {
|
|
@@ -358,7 +437,7 @@ export async function editElement(
|
|
|
358
437
|
: `Could not save (status ${response.status}). Check canon canvas serve is still running.`)
|
|
359
438
|
reloadFrame(key)
|
|
360
439
|
await loadPages(fetchImpl)
|
|
361
|
-
return
|
|
440
|
+
return undefined
|
|
362
441
|
}
|
|
363
442
|
editRefusal.value = undefined
|
|
364
443
|
const saved = pendingEdit.value
|
|
@@ -366,18 +445,51 @@ export async function editElement(
|
|
|
366
445
|
setTimeout(() => {
|
|
367
446
|
if (savedEdit.value === saved) savedEdit.value = undefined
|
|
368
447
|
}, SAVED_MS)
|
|
369
|
-
if (typeof record.hash
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
448
|
+
if (typeof record.hash !== 'string') return {}
|
|
449
|
+
const hashes = new Map(editedHashes.value)
|
|
450
|
+
hashes.set(key, record.hash)
|
|
451
|
+
editedHashes.value = hashes
|
|
452
|
+
return { hash: record.hash }
|
|
374
453
|
} catch (error) {
|
|
375
454
|
editRefusal.value = `Could not save (${error instanceof Error ? error.message : 'unknown'}). Check canon canvas serve is still running.`
|
|
455
|
+
return undefined
|
|
376
456
|
} finally {
|
|
377
457
|
pendingEdit.value = undefined
|
|
378
458
|
}
|
|
379
459
|
}
|
|
380
460
|
|
|
461
|
+
/**
|
|
462
|
+
* Writes an element's new size as one edit per changed axis, width first. The
|
|
463
|
+
* second carries the hash the first answered, so a frame reloading between the
|
|
464
|
+
* two cannot leave it holding the hash of a file already rewritten. A refused
|
|
465
|
+
* first edit sends no second.
|
|
466
|
+
*/
|
|
467
|
+
export async function resizeElement(
|
|
468
|
+
ref: FrameRef,
|
|
469
|
+
key: string,
|
|
470
|
+
address: ElementAddress,
|
|
471
|
+
width: number | undefined,
|
|
472
|
+
height: number | undefined,
|
|
473
|
+
fetchImpl: typeof fetch = fetch,
|
|
474
|
+
): Promise<void> {
|
|
475
|
+
let current = address
|
|
476
|
+
if (width !== undefined) {
|
|
477
|
+
const written = await editElement(
|
|
478
|
+
ref,
|
|
479
|
+
key,
|
|
480
|
+
current,
|
|
481
|
+
'width',
|
|
482
|
+
`${width}px`,
|
|
483
|
+
fetchImpl,
|
|
484
|
+
)
|
|
485
|
+
if (!written) return
|
|
486
|
+
if (written.hash) current = { ...current, hash: written.hash }
|
|
487
|
+
}
|
|
488
|
+
if (height !== undefined) {
|
|
489
|
+
await editElement(ref, key, current, 'height', `${height}px`, fetchImpl)
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
381
493
|
/**
|
|
382
494
|
* Moves a frame on the surface without writing, which a drag calls on every
|
|
383
495
|
* pointer move. The next reread replaces it with what the layout holds.
|
|
@@ -406,6 +518,32 @@ export async function moveFrameTo(
|
|
|
406
518
|
await write('/api/frames/move', { ...ref, x, y }, fetchImpl)
|
|
407
519
|
}
|
|
408
520
|
|
|
521
|
+
/** Resizes a frame on the surface without writing, as a handle drag does. */
|
|
522
|
+
export function previewResize(ref: FrameRef, box: Box): void {
|
|
523
|
+
pages.value = pages.value.map((candidate) =>
|
|
524
|
+
candidate.name === ref.page
|
|
525
|
+
? {
|
|
526
|
+
...candidate,
|
|
527
|
+
frames: candidate.frames.map((frame) =>
|
|
528
|
+
frame.name === ref.frame
|
|
529
|
+
? { ...frame, ...box, placed: true }
|
|
530
|
+
: frame,
|
|
531
|
+
),
|
|
532
|
+
}
|
|
533
|
+
: candidate,
|
|
534
|
+
)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/** Shows the new box and writes it through the server's shared layout writer. */
|
|
538
|
+
export async function resizeFrameTo(
|
|
539
|
+
ref: FrameRef,
|
|
540
|
+
box: Box,
|
|
541
|
+
fetchImpl: typeof fetch = fetch,
|
|
542
|
+
): Promise<void> {
|
|
543
|
+
previewResize(ref, box)
|
|
544
|
+
await write('/api/frames/resize', { ...ref, ...box }, fetchImpl)
|
|
545
|
+
}
|
|
546
|
+
|
|
409
547
|
export function clampZoom(zoom: number): number {
|
|
410
548
|
return Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom))
|
|
411
549
|
}
|
|
@@ -427,11 +565,19 @@ export function panBy(dx: number, dy: number): void {
|
|
|
427
565
|
view.value = { ...current, x: current.x + dx, y: current.y + dy }
|
|
428
566
|
}
|
|
429
567
|
|
|
430
|
-
/**
|
|
431
|
-
|
|
568
|
+
/**
|
|
569
|
+
* Fits every frame on the current page into a viewport of the given size,
|
|
570
|
+
* clear of `insetLeft` pixels of chrome floating over its left edge.
|
|
571
|
+
*/
|
|
572
|
+
export function fitView(
|
|
573
|
+
width: number,
|
|
574
|
+
height: number,
|
|
575
|
+
padding = 48,
|
|
576
|
+
insetLeft = 0,
|
|
577
|
+
): void {
|
|
432
578
|
const frames = currentPage.value?.frames ?? []
|
|
433
579
|
if (frames.length === 0 || width <= 0 || height <= 0) {
|
|
434
|
-
view.value = { x: padding, y: padding, zoom: 0.5 }
|
|
580
|
+
view.value = { x: insetLeft + padding, y: padding, zoom: 0.5 }
|
|
435
581
|
return
|
|
436
582
|
}
|
|
437
583
|
const left = Math.min(...frames.map((frame) => frame.x))
|
|
@@ -440,14 +586,14 @@ export function fitView(width: number, height: number, padding = 48): void {
|
|
|
440
586
|
const bottom = Math.max(...frames.map((frame) => frame.y + frame.height))
|
|
441
587
|
const zoom = clampZoom(
|
|
442
588
|
Math.min(
|
|
443
|
-
(width - padding * 2) / (right - left),
|
|
589
|
+
(width - insetLeft - padding * 2) / (right - left),
|
|
444
590
|
(height - padding * 2) / (bottom - top),
|
|
445
591
|
1,
|
|
446
592
|
),
|
|
447
593
|
)
|
|
448
594
|
view.value = {
|
|
449
595
|
zoom,
|
|
450
|
-
x: padding - left * zoom,
|
|
596
|
+
x: insetLeft + padding - left * zoom,
|
|
451
597
|
y: padding - top * zoom,
|
|
452
598
|
}
|
|
453
599
|
}
|
|
@@ -477,10 +623,15 @@ export function resetState(): void {
|
|
|
477
623
|
selection.value = undefined
|
|
478
624
|
frameDocuments.value = new Map()
|
|
479
625
|
hoveredElement.value = undefined
|
|
626
|
+
editingFrames.value = new Map()
|
|
627
|
+
clearTimeout(editingTimer)
|
|
628
|
+
editingTimer = undefined
|
|
480
629
|
expandedFrames.value = new Set()
|
|
481
630
|
draggingFrame.value = undefined
|
|
482
631
|
writeError.value = undefined
|
|
483
632
|
leftTab.value = 'pages'
|
|
633
|
+
activeTool.value = 'move'
|
|
634
|
+
spacePan.value = undefined
|
|
484
635
|
pendingEdit.value = undefined
|
|
485
636
|
savedEdit.value = undefined
|
|
486
637
|
editRefusal.value = undefined
|