Building 4 × (3 min at threshold / 1 min ramp / 2 min easy) means fighting the editor, because a step inside a repeat can do less than the same step outside one.
Mockups: https://claude.ai/code/artifact/6c6f89dc-e3e6-4370-a690-7d792c7ee667 — middle column.
|
top-level step |
inside a repeat |
| drag to reorder |
yes |
no — draggable={top} (edit/+page.svelte:312) |
| what you can add |
steady · ramp · sprint · repeat |
steady only (addInto(), :151) |
| click it on the graph |
selects it |
selects its parent — onSelect={(i) => (selected = [i])} (:263) |
| duplicate it |
no |
no |
| a context menu |
no |
no |
The state layer is not the problem: stepAt() already walks an arbitrary path, move() and remove() already take a path and work at any depth, and siblingsOf() already resolves a parent's children. Three UI affordances stopped at one level while the model underneath never did.
addInto() even says so out loud:
// ponytail: steady only — over-unders are steady pairs; other types via top-level
That was a fair shortcut when repeats were new. It is now the reason a rider cannot put a ramp inside an interval set.
What changes
Drag works at every depth. The current handler indexes workout.steps directly (dropStep(), :164) — it becomes path-based, dropping within the same parent. Cross-parent drags (out of a repeat, or into one) are the harder case: allow it if it falls out cheaply, and if not, say so in a ponytail: comment naming the limit rather than leaving it silently missing.
Every step type can be added anywhere. addInto() takes the type, and the nested add row renders the same four buttons the top level does.
The graph selects the step you clicked, at whatever depth. IntervalGraph's onSelect carries a path rather than a top-level index — flatten() already knows which step produced each segment; the block needs to carry that path out.
Duplicate exists, for a step and for a whole repeat block. This is the single biggest lever on "clunky": an interval set is built by making one block right and then copying it.
A step gets a context menu — ux.md requires it for any object with more than one action, and a step has five. Right-click on a desk, long-press on touch, using contextMenu from $lib/context-menu.svelte:
┌────────────────────────────────┐
│ ⧉ Duplicate ⌘D │
│ ↑ Move up ⌘↑ │
│ ↓ Move down ⌘↓ │
├────────────────────────────────┤
│ ⟲ Wrap in a repeat │
├────────────────────────────────┤
│ 🗑 Delete ⌫ │ ← danger token, last
└────────────────────────────────┘
Nothing lives only in the menu: the arrows stay inline, delete stays in the inspector, and duplicate gets a visible affordance too.
Structure
edit/+page.svelte is 583 lines against code-quality.md's 500-line ceiling, and this adds to it. Split the surface this issue touches, in this PR:
edit/+page.svelte → page shell, workout state, save
StepList.svelte → the tree, drag at depth, the context menu
Leave the inspector and the library sidebar for the issues that touch them.
Acceptance criteria
Verify it for real
The verify skill: build 4 × (3 min threshold / 1 min ramp / 2 min easy) from an empty sheet without touching the top-level add buttons for the inner steps, duplicate the repeat, and confirm the graph and the zone bar follow.
Deliberately not in scope
Undo (#1005), direct manipulation on the graph (#1006), the hardcoded FTP (#1003), and the library sidebar's one-click replacement of your sheet.
Related
#1003, #1005, #1006, #255 (the previous intuitiveness pass — this is what it did not reach), #170, ux.md (context menus), code-quality.md (the size ceiling).
Building
4 × (3 min at threshold / 1 min ramp / 2 min easy)means fighting the editor, because a step inside a repeat can do less than the same step outside one.Mockups: https://claude.ai/code/artifact/6c6f89dc-e3e6-4370-a690-7d792c7ee667 — middle column.
draggable={top}(edit/+page.svelte:312)addInto(),:151)onSelect={(i) => (selected = [i])}(:263)The state layer is not the problem:
stepAt()already walks an arbitrary path,move()andremove()already take a path and work at any depth, andsiblingsOf()already resolves a parent's children. Three UI affordances stopped at one level while the model underneath never did.addInto()even says so out loud:// ponytail: steady only — over-unders are steady pairs; other types via top-levelThat was a fair shortcut when repeats were new. It is now the reason a rider cannot put a ramp inside an interval set.
What changes
Drag works at every depth. The current handler indexes
workout.stepsdirectly (dropStep(),:164) — it becomes path-based, dropping within the same parent. Cross-parent drags (out of a repeat, or into one) are the harder case: allow it if it falls out cheaply, and if not, say so in aponytail:comment naming the limit rather than leaving it silently missing.Every step type can be added anywhere.
addInto()takes the type, and the nested add row renders the same four buttons the top level does.The graph selects the step you clicked, at whatever depth.
IntervalGraph'sonSelectcarries a path rather than a top-level index —flatten()already knows which step produced each segment; the block needs to carry that path out.Duplicate exists, for a step and for a whole repeat block. This is the single biggest lever on "clunky": an interval set is built by making one block right and then copying it.
A step gets a context menu —
ux.mdrequires it for any object with more than one action, and a step has five. Right-click on a desk, long-press on touch, usingcontextMenufrom$lib/context-menu.svelte:Nothing lives only in the menu: the arrows stay inline, delete stays in the inspector, and duplicate gets a visible affordance too.
Structure
edit/+page.svelteis 583 lines againstcode-quality.md's 500-line ceiling, and this adds to it. Split the surface this issue touches, in this PR:Leave the inspector and the library sidebar for the issues that touch them.
Acceptance criteria
ponytail:comment naming the limit.StepList.svelteis split out andedit/+page.svelteis back under 500 lines.make cigreen.Verify it for real
The
verifyskill: build4 × (3 min threshold / 1 min ramp / 2 min easy)from an empty sheet without touching the top-level add buttons for the inner steps, duplicate the repeat, and confirm the graph and the zone bar follow.Deliberately not in scope
Undo (#1005), direct manipulation on the graph (#1006), the hardcoded FTP (#1003), and the library sidebar's one-click replacement of your sheet.
Related
#1003, #1005, #1006, #255 (the previous intuitiveness pass — this is what it did not reach), #170,
ux.md(context menus),code-quality.md(the size ceiling).