@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
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/** @jsxImportSource preact */
|
|
2
|
+
import type { JSX } from 'preact'
|
|
3
|
+
import { useRef, useState } from 'preact/hooks'
|
|
4
|
+
import { shownTool, view } from '@/canvas/client/state'
|
|
5
|
+
|
|
6
|
+
/** Screen pixels a press travels before it reads as a drag and not a click. */
|
|
7
|
+
export const DRAG_THRESHOLD = 3
|
|
8
|
+
|
|
9
|
+
/** The smallest box a handle drag leaves, in surface units. */
|
|
10
|
+
const MIN_SIZE = 1
|
|
11
|
+
|
|
12
|
+
export type Corner = 'nw' | 'ne' | 'sw' | 'se'
|
|
13
|
+
|
|
14
|
+
const CORNERS: readonly Corner[] = ['nw', 'ne', 'sw', 'se']
|
|
15
|
+
|
|
16
|
+
export interface OverlayBox {
|
|
17
|
+
readonly x: number
|
|
18
|
+
readonly y: number
|
|
19
|
+
readonly width: number
|
|
20
|
+
readonly height: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The box a corner drag leaves, with the delta already in surface units. The
|
|
25
|
+
* corner opposite the one dragged stays put, so a west or north handle moves
|
|
26
|
+
* the box as it resizes it.
|
|
27
|
+
*/
|
|
28
|
+
export function resizedBox(
|
|
29
|
+
origin: OverlayBox,
|
|
30
|
+
corner: Corner,
|
|
31
|
+
dx: number,
|
|
32
|
+
dy: number,
|
|
33
|
+
): OverlayBox {
|
|
34
|
+
const isWest = corner === 'nw' || corner === 'sw'
|
|
35
|
+
const isNorth = corner === 'nw' || corner === 'ne'
|
|
36
|
+
const width = Math.max(MIN_SIZE, origin.width + (isWest ? -dx : dx))
|
|
37
|
+
const height = Math.max(MIN_SIZE, origin.height + (isNorth ? -dy : dy))
|
|
38
|
+
return {
|
|
39
|
+
x: isWest ? origin.x + origin.width - width : origin.x,
|
|
40
|
+
y: isNorth ? origin.y + origin.height - height : origin.y,
|
|
41
|
+
width,
|
|
42
|
+
height,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function sizeLabel(box: Pick<OverlayBox, 'width' | 'height'>): string {
|
|
47
|
+
return `${Math.round(box.width)} × ${Math.round(box.height)}`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface HandleGesture {
|
|
51
|
+
readonly corner: Corner
|
|
52
|
+
readonly startX: number
|
|
53
|
+
readonly startY: number
|
|
54
|
+
readonly origin: OverlayBox
|
|
55
|
+
box: OverlayBox
|
|
56
|
+
moved: boolean
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface SelectionOverlayProps {
|
|
60
|
+
readonly kind: 'frame' | 'element'
|
|
61
|
+
/** Read on every render, so a box the drag previewed is what shows after. */
|
|
62
|
+
readonly measure: () => OverlayBox
|
|
63
|
+
/** Subtracted from the box to place the overlay inside its container. */
|
|
64
|
+
readonly offset?: { readonly x: number; readonly y: number }
|
|
65
|
+
readonly hasChip?: boolean
|
|
66
|
+
/**
|
|
67
|
+
* Draws the target's own measured box during a drag rather than the dragged
|
|
68
|
+
* one. An element in normal flow grows from its own top left whichever corner
|
|
69
|
+
* is dragged, so only its measured box shows where it actually sits.
|
|
70
|
+
*/
|
|
71
|
+
readonly isMeasuredWhileDragging?: boolean
|
|
72
|
+
readonly onStart?: () => void
|
|
73
|
+
readonly onPreview?: (box: OverlayBox, origin: OverlayBox) => void
|
|
74
|
+
readonly onCommit: (box: OverlayBox, origin: OverlayBox) => void
|
|
75
|
+
readonly onCancel?: (origin: OverlayBox) => void
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The selected frame's or element's outline, its four corner handles, and an
|
|
80
|
+
* element's size chip. Every part sizes against `--outline-scale`, so it keeps
|
|
81
|
+
* one screen weight at any zoom.
|
|
82
|
+
*/
|
|
83
|
+
export function SelectionOverlay({
|
|
84
|
+
kind,
|
|
85
|
+
measure,
|
|
86
|
+
offset = { x: 0, y: 0 },
|
|
87
|
+
hasChip = false,
|
|
88
|
+
isMeasuredWhileDragging = false,
|
|
89
|
+
onStart,
|
|
90
|
+
onPreview,
|
|
91
|
+
onCommit,
|
|
92
|
+
onCancel,
|
|
93
|
+
}: SelectionOverlayProps): JSX.Element {
|
|
94
|
+
const gesture = useRef<HandleGesture | undefined>(undefined)
|
|
95
|
+
const [dragged, setDragged] = useState<OverlayBox | undefined>(undefined)
|
|
96
|
+
const box = dragged ?? measure()
|
|
97
|
+
|
|
98
|
+
const handlePointerDown = (corner: Corner) => (event: PointerEvent) => {
|
|
99
|
+
if (event.button !== 0) return
|
|
100
|
+
/* A pan drag starting on a handle belongs to the surface behind it. */
|
|
101
|
+
if (shownTool.value === 'pan') return
|
|
102
|
+
event.preventDefault()
|
|
103
|
+
event.stopPropagation()
|
|
104
|
+
const origin = measure()
|
|
105
|
+
gesture.current = {
|
|
106
|
+
corner,
|
|
107
|
+
startX: event.clientX,
|
|
108
|
+
startY: event.clientY,
|
|
109
|
+
origin,
|
|
110
|
+
box: origin,
|
|
111
|
+
moved: false,
|
|
112
|
+
}
|
|
113
|
+
;(event.currentTarget as HTMLElement).setPointerCapture?.(event.pointerId)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const handlePointerMove = (event: PointerEvent) => {
|
|
117
|
+
const current = gesture.current
|
|
118
|
+
if (!current) return
|
|
119
|
+
const dx = event.clientX - current.startX
|
|
120
|
+
const dy = event.clientY - current.startY
|
|
121
|
+
if (!current.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return
|
|
122
|
+
if (!current.moved) onStart?.()
|
|
123
|
+
current.moved = true
|
|
124
|
+
/* The delta is in screen pixels and the box in surface units. */
|
|
125
|
+
const { zoom } = view.value
|
|
126
|
+
current.box = resizedBox(
|
|
127
|
+
current.origin,
|
|
128
|
+
current.corner,
|
|
129
|
+
Math.round(dx / zoom),
|
|
130
|
+
Math.round(dy / zoom),
|
|
131
|
+
)
|
|
132
|
+
onPreview?.(current.box, current.origin)
|
|
133
|
+
setDragged(isMeasuredWhileDragging ? measure() : current.box)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const handlePointerUp = () => {
|
|
137
|
+
const current = gesture.current
|
|
138
|
+
gesture.current = undefined
|
|
139
|
+
if (current?.moved) onCommit(current.box, current.origin)
|
|
140
|
+
setDragged(undefined)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const handlePointerCancel = () => {
|
|
144
|
+
const current = gesture.current
|
|
145
|
+
gesture.current = undefined
|
|
146
|
+
if (current?.moved) onCancel?.(current.origin)
|
|
147
|
+
setDragged(undefined)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div
|
|
152
|
+
class="selection"
|
|
153
|
+
data-selection={kind}
|
|
154
|
+
data-resizing={dragged ? 'true' : undefined}
|
|
155
|
+
style={{
|
|
156
|
+
left: `${box.x - offset.x}px`,
|
|
157
|
+
top: `${box.y - offset.y}px`,
|
|
158
|
+
width: `${box.width}px`,
|
|
159
|
+
height: `${box.height}px`,
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
{CORNERS.map((corner) => (
|
|
163
|
+
<span
|
|
164
|
+
key={corner}
|
|
165
|
+
class="selection-handle"
|
|
166
|
+
data-handle={corner}
|
|
167
|
+
aria-hidden="true"
|
|
168
|
+
onPointerDown={handlePointerDown(corner)}
|
|
169
|
+
onPointerMove={handlePointerMove}
|
|
170
|
+
onPointerUp={handlePointerUp}
|
|
171
|
+
onPointerCancel={handlePointerCancel}
|
|
172
|
+
/>
|
|
173
|
+
))}
|
|
174
|
+
{hasChip ? (
|
|
175
|
+
<span class="selection-chip" aria-hidden="true">
|
|
176
|
+
{sizeLabel(box)}
|
|
177
|
+
</span>
|
|
178
|
+
) : null}
|
|
179
|
+
</div>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** The element under the pointer, drawn without handles. */
|
|
184
|
+
export function HoverOutline({ element }: { readonly element: Element }) {
|
|
185
|
+
const rect = element.getBoundingClientRect()
|
|
186
|
+
return (
|
|
187
|
+
<div
|
|
188
|
+
class="element-outline"
|
|
189
|
+
data-outline="hover"
|
|
190
|
+
aria-hidden="true"
|
|
191
|
+
style={{
|
|
192
|
+
left: `${rect.left}px`,
|
|
193
|
+
top: `${rect.top}px`,
|
|
194
|
+
width: `${rect.width}px`,
|
|
195
|
+
height: `${rect.height}px`,
|
|
196
|
+
}}
|
|
197
|
+
/>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
@@ -217,6 +217,27 @@ code {
|
|
|
217
217
|
cursor: grabbing;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
/* Inset, so the panels on either side never clip the ring. */
|
|
221
|
+
.surface:focus-visible {
|
|
222
|
+
outline-offset: -2px;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/*
|
|
226
|
+
* While panning, a frame takes no pointer, so a drag over one moves the view
|
|
227
|
+
* rather than picking an element, and the cursor reads the viewport's grab.
|
|
228
|
+
* The descendants are named too, since a selection handle sets its own
|
|
229
|
+
* pointer events and would otherwise take the drag as a resize. Nothing in
|
|
230
|
+
* the viewport starts a text selection while a pan drags across it.
|
|
231
|
+
*/
|
|
232
|
+
.surface[data-tool='pan'] .viewport {
|
|
233
|
+
user-select: none;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.surface[data-tool='pan'] .frame,
|
|
237
|
+
.surface[data-tool='pan'] .frame * {
|
|
238
|
+
pointer-events: none;
|
|
239
|
+
}
|
|
240
|
+
|
|
220
241
|
.plane {
|
|
221
242
|
position: absolute;
|
|
222
243
|
top: 0;
|
|
@@ -237,14 +258,33 @@ code {
|
|
|
237
258
|
right: 0;
|
|
238
259
|
display: flex;
|
|
239
260
|
align-items: center;
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
261
|
+
/*
|
|
262
|
+
* Sized against the zoom, so the label reads at one screen size at any zoom.
|
|
263
|
+
* A row too short for every part wraps the theme switch, then the editing
|
|
264
|
+
* badge, then the size onto lines the clip hides, so the name keeps the row
|
|
265
|
+
* longest.
|
|
266
|
+
*/
|
|
267
|
+
flex-wrap: wrap;
|
|
268
|
+
align-content: flex-start;
|
|
269
|
+
column-gap: calc(6px * var(--outline-scale, 1));
|
|
270
|
+
row-gap: calc(40px * var(--outline-scale, 1));
|
|
271
|
+
height: calc(25px * var(--outline-scale, 1));
|
|
272
|
+
overflow: clip;
|
|
273
|
+
overflow-clip-margin: calc(4px * var(--outline-scale, 1));
|
|
274
|
+
padding-bottom: calc(6px * var(--outline-scale, 1));
|
|
275
|
+
font-size: calc(11px * var(--outline-scale, 1));
|
|
276
|
+
line-height: 1.3;
|
|
243
277
|
color: var(--color-text-secondary);
|
|
244
278
|
white-space: nowrap;
|
|
245
279
|
}
|
|
246
280
|
|
|
281
|
+
/* A theme switch focused on a hidden line shows rather than staying hidden. */
|
|
282
|
+
.frame-label:focus-within {
|
|
283
|
+
overflow: visible;
|
|
284
|
+
}
|
|
285
|
+
|
|
247
286
|
.frame-name {
|
|
287
|
+
min-width: 0;
|
|
248
288
|
overflow: hidden;
|
|
249
289
|
text-overflow: ellipsis;
|
|
250
290
|
color: var(--color-text);
|
|
@@ -255,10 +295,45 @@ code {
|
|
|
255
295
|
font-variant-numeric: tabular-nums;
|
|
256
296
|
}
|
|
257
297
|
|
|
298
|
+
/*
|
|
299
|
+
* Ahead of the theme switch, so a row too short for both wraps the switch
|
|
300
|
+
* first. The editing outline still marks the frame when the badge wraps too.
|
|
301
|
+
*/
|
|
302
|
+
.frame-editing {
|
|
303
|
+
display: inline-flex;
|
|
304
|
+
align-items: center;
|
|
305
|
+
gap: calc(4px * var(--outline-scale, 1));
|
|
306
|
+
padding: calc(1px * var(--outline-scale, 1))
|
|
307
|
+
calc(6px * var(--outline-scale, 1));
|
|
308
|
+
border-radius: var(--radius-pill);
|
|
309
|
+
background: color-mix(in srgb, var(--color-success) 16%, transparent);
|
|
310
|
+
color: var(--color-success);
|
|
311
|
+
font-weight: 600;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.frame-editing:empty {
|
|
315
|
+
display: none;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.frame-editing::before {
|
|
319
|
+
content: '';
|
|
320
|
+
width: calc(6px * var(--outline-scale, 1));
|
|
321
|
+
height: calc(6px * var(--outline-scale, 1));
|
|
322
|
+
border-radius: 50%;
|
|
323
|
+
background: currentColor;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* Dashed, so it reads apart from the solid accent of a selection. */
|
|
327
|
+
.frame[data-editing='true'] iframe {
|
|
328
|
+
outline: calc(2px * var(--outline-scale, 1)) dashed var(--color-success);
|
|
329
|
+
outline-offset: calc(2px * var(--outline-scale, 1));
|
|
330
|
+
}
|
|
331
|
+
|
|
258
332
|
.frame-theme {
|
|
259
333
|
margin-left: auto;
|
|
260
|
-
padding:
|
|
261
|
-
|
|
334
|
+
padding: calc(1px * var(--outline-scale, 1))
|
|
335
|
+
calc(6px * var(--outline-scale, 1));
|
|
336
|
+
border: calc(1px * var(--outline-scale, 1)) solid var(--color-border);
|
|
262
337
|
border-radius: var(--radius-pill);
|
|
263
338
|
background: var(--color-surface);
|
|
264
339
|
color: var(--color-text-secondary);
|
|
@@ -301,18 +376,91 @@ code {
|
|
|
301
376
|
outline: calc(1px * var(--outline-scale, 1)) dashed var(--color-accent);
|
|
302
377
|
}
|
|
303
378
|
|
|
304
|
-
.element-outline[data-outline='selected'] {
|
|
305
|
-
outline: calc(2px * var(--outline-scale, 1)) solid var(--color-accent);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
379
|
/* Above its neighbors, so a frame dragged over another stays whole. */
|
|
309
380
|
.frame[data-selected='true'] {
|
|
310
381
|
z-index: 1;
|
|
311
382
|
}
|
|
312
383
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
384
|
+
/* A selected frame names itself in the accent, and a picked element does not. */
|
|
385
|
+
.frame[data-selected='true']:not([data-picked]) .frame-name {
|
|
386
|
+
color: var(--color-accent);
|
|
387
|
+
font-weight: 600;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/*
|
|
391
|
+
* The selected frame's or element's outline, handles, and size chip. Every
|
|
392
|
+
* length multiplies by the outline scale, so each holds one screen size.
|
|
393
|
+
*/
|
|
394
|
+
.selection {
|
|
395
|
+
--handle: calc(8px * var(--outline-scale, 1));
|
|
396
|
+
--handle-hit: calc(24px * var(--outline-scale, 1));
|
|
397
|
+
position: absolute;
|
|
398
|
+
pointer-events: none;
|
|
399
|
+
outline: calc(1px * var(--outline-scale, 1)) solid var(--color-accent);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/* Inset by the iframe border, so the box lines up with the element it marks. */
|
|
403
|
+
.selection[data-selection='element'] {
|
|
404
|
+
translate: 1px 1px;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/* A 24 pixel hit area around an 8 pixel square, per the target size minimum. */
|
|
408
|
+
.selection-handle {
|
|
409
|
+
position: absolute;
|
|
410
|
+
width: var(--handle-hit);
|
|
411
|
+
height: var(--handle-hit);
|
|
412
|
+
margin: calc(var(--handle-hit) / -2);
|
|
413
|
+
pointer-events: auto;
|
|
414
|
+
touch-action: none;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.selection-handle::after {
|
|
418
|
+
content: '';
|
|
419
|
+
position: absolute;
|
|
420
|
+
inset: calc((var(--handle-hit) - var(--handle)) / 2);
|
|
421
|
+
border: calc(1px * var(--outline-scale, 1)) solid var(--color-accent);
|
|
422
|
+
background: var(--color-background);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.selection-handle[data-handle='nw'] {
|
|
426
|
+
top: 0;
|
|
427
|
+
left: 0;
|
|
428
|
+
cursor: nwse-resize;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.selection-handle[data-handle='ne'] {
|
|
432
|
+
top: 0;
|
|
433
|
+
left: 100%;
|
|
434
|
+
cursor: nesw-resize;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.selection-handle[data-handle='sw'] {
|
|
438
|
+
top: 100%;
|
|
439
|
+
left: 0;
|
|
440
|
+
cursor: nesw-resize;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.selection-handle[data-handle='se'] {
|
|
444
|
+
top: 100%;
|
|
445
|
+
left: 100%;
|
|
446
|
+
cursor: nwse-resize;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.selection-chip {
|
|
450
|
+
position: absolute;
|
|
451
|
+
top: calc(100% + 8px * var(--outline-scale, 1));
|
|
452
|
+
left: 50%;
|
|
453
|
+
translate: -50% 0;
|
|
454
|
+
padding: calc(2px * var(--outline-scale, 1))
|
|
455
|
+
calc(5px * var(--outline-scale, 1));
|
|
456
|
+
border-radius: calc(3px * var(--outline-scale, 1));
|
|
457
|
+
background: var(--color-accent);
|
|
458
|
+
color: var(--color-background);
|
|
459
|
+
font-family: var(--type-code-family);
|
|
460
|
+
font-size: calc(11px * var(--outline-scale, 1));
|
|
461
|
+
line-height: 1.3;
|
|
462
|
+
font-variant-numeric: tabular-nums;
|
|
463
|
+
white-space: nowrap;
|
|
316
464
|
}
|
|
317
465
|
|
|
318
466
|
.frame:focus-visible {
|
|
@@ -333,6 +481,56 @@ code {
|
|
|
333
481
|
background: var(--color-chrome);
|
|
334
482
|
}
|
|
335
483
|
|
|
484
|
+
.tools {
|
|
485
|
+
position: absolute;
|
|
486
|
+
top: var(--space-md);
|
|
487
|
+
left: var(--space-md);
|
|
488
|
+
display: flex;
|
|
489
|
+
flex-direction: column;
|
|
490
|
+
gap: 2px;
|
|
491
|
+
padding: var(--space-xs);
|
|
492
|
+
border: 1px solid var(--color-border);
|
|
493
|
+
border-radius: var(--radius-panel);
|
|
494
|
+
background: var(--color-chrome);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.tool-button {
|
|
498
|
+
display: grid;
|
|
499
|
+
place-items: center;
|
|
500
|
+
width: 28px;
|
|
501
|
+
height: 28px;
|
|
502
|
+
padding: 0;
|
|
503
|
+
border: 0;
|
|
504
|
+
border-radius: var(--radius-action);
|
|
505
|
+
background: transparent;
|
|
506
|
+
color: var(--color-text-secondary);
|
|
507
|
+
cursor: pointer;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.tool-button svg {
|
|
511
|
+
width: 16px;
|
|
512
|
+
height: 16px;
|
|
513
|
+
fill: none;
|
|
514
|
+
stroke: currentColor;
|
|
515
|
+
stroke-width: 1.3;
|
|
516
|
+
stroke-linejoin: round;
|
|
517
|
+
stroke-linecap: round;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.tool-button[data-tool='move'] svg {
|
|
521
|
+
fill: currentColor;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.tool-button:hover {
|
|
525
|
+
color: var(--color-text);
|
|
526
|
+
background: var(--color-surface);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.tool-button[aria-pressed='true'] {
|
|
530
|
+
color: var(--color-text);
|
|
531
|
+
background: var(--color-border);
|
|
532
|
+
}
|
|
533
|
+
|
|
336
534
|
.zoom {
|
|
337
535
|
min-width: 3.5rem;
|
|
338
536
|
text-align: center;
|