Skip to content

feat(web): group questions into screens in the builder - #206

Merged
josue-commits merged 16 commits into
developfrom
feat/screen-groups-editor
Sep 26, 2026
Merged

josue-commits merged 16 commits into
developfrom
feat/screen-groups-editor

Conversation

@josue-commits

Copy link
Copy Markdown
Contributor

Closes #200 (2 of 2).

Stacked on the engine PR (#205). This PR targets feat/screen-groups-engine-renderer. Merge it after that PR, then retarget it to develop.

This is the builder side of screens. Authors join a question to the screen above it, and the screen then shows as one unit everywhere the builder shows the form.

What it adds

  • Question list (spine).
    • A chain toggle between two rows joins or splits them. It shows on hover or focus, and it stays visible while the rows are joined.
    • Joined rows share borders, and the first row carries a "Screen N · X questions" chip. Question numbers stay per question.
    • Dragging:
      • within a screen, it reorders;
      • dropping between two members joins the screen;
      • dropping anywhere else leaves it;
      • a question that can't join, dropped inside a screen, lands right after it.
    • Mid-drag, rows move by translation only (no stretching), and the dragged row lifts as its own card. Screen names and chains step aside until the drop.
    • There is no toggle above the first shown question, because nothing can join there.
    • A selected or hovered row draws one outline that follows its shape inside the screen.
  • Settings switch. "Show on the same screen as the question above" is always visible, after the Logic card. Below lg the spine is hidden, so this switch is the only way to group there.
    • It can't apply to the first question, schedulers, reveal screens, file uploads, hidden questions, or a screen that already has 10 questions.
    • In those cases it stays focusable and says why.
  • Canvas. Selecting a member draws its whole screen as one card. The card is keyed by screen, so switching between members doesn't remount it.
  • Preview. The preview walks screen by screen, and "Step N of M" counts screens.
  • Logic.
    • Jump targets offer only screen starts.
    • Notes say that a jump runs when the respondent leaves the screen, and warn about an existing rule that targets the middle of a screen or its own screen.
    • The logic canvas draws a frame around each screen, and the map names it.
    • Each select keeps only its own stale value, and new rules default to a target that is actually offered.
  • Keeping screens valid. Every edit runs the same normalization: add, delete, reorder, type change, hide or show, layout switch, and restoring a recovered draft. So:
    • no edit leaves a broken screen;
    • the partial point stays after its screen;
    • un-hiding a question into a full screen, or un-hiding a scheduler, reveal or file upload, places it right after the screen.
  • One page. The screen controls are hidden, and a note explains that screens only apply to slides. The ids stay in the config, so switching back to slides restores the screens.
  • Copy in English and Spanish, including the layout descriptions in the create form and the design panel.

Notes for review

  • PM decisions:
    • the switch is always visible, not inside Advanced settings;
    • on a multi-question screen, a message heading keeps the slide title size and doesn't take the initial focus;
    • the solo-type copy uses the gallery's names ("Schedulers" / "agendadores").
  • Scheduler panel: the after-booking picker now labels an untitled target "Question N" instead of showing its raw key. This applies to every form.
  • One page: drag reordering applies the same join and leave rules, so switching back to slides never finds a broken screen.

Testing

  • pnpm typecheck, pnpm lint and pnpm test pass; web has 1122 tests.
  • e2e: v19-screens covers the builder: the spine toggle, the Settings switch, the screen canvas, the preview by screens and the jump target list.
    • Chromium, with v18 and v17 in the same run: 24 passed.
    • WebKit: the only failure is the builder preview test that already fails on the base.
    • After the final spine polish, the v19 builder cases were run again in Chromium: 3/3.
  • Publish gate passed, no dashes added, DCO and commitlint clean.

🤖 Generated with Claude Code

josue-commits and others added 16 commits September 25, 2026 15:38
…ne page

The slides description, in the create-form dialog and in Design, now says
what slides do with screens: "One screen at a time. Group questions to show
several on one screen." A form with screens switched to One page gets a
note under the layout picker: screens only apply to Slides, and they are
kept, so switching back restores them. EN and ES, with a changeset for
@quill/shared.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
What every builder surface needs to agree on, in one pure module
(screen-util.ts) with its spec:

- whether the boundary above a question can be joined, and why not: no
  question shown above it, a calendar, reveal or file upload, a hidden
  question, or a full screen of 10;
- the screens in author order, which the chips and the progress count;
- where a dragged question lands: it stays within its own screen, joins a
  screen when dropped between two of its questions, and leaves it anywhere
  else; one that cannot join lands right after the screen instead of
  cutting it in two, and a hidden one stays put, since it cuts nothing;
- a question shown again rejoining the screen around it;
- where the partial point, and a "reveal after" card, go around a screen,
  and the partial point staying with its screen when its anchor is dragged
  out;
- where a jump lands: inside its own screen (ignored) or mid-screen (the
  whole screen opens);
- `withScreens`, the canonical pass the editor runs on open and after every
  edit; it returns the very same config for every frozen legacy fixture and
  template, on both layouts, so opening one is not an edit.

Plus the notes those surfaces show and all their copy, EN and ES.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
The preview's stepper counts screens: a screen of several questions is one
stop, and "Step N of M" reads screens. The frame is still asked to start on
a step index, the first of the screen, so the preview protocol does not
change. A form without screens has one step per screen, so its numbers are
the ones it always had. The math is a pure `previewNav` with its spec,
cover, clamping and an empty form included.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
`CanvasScreen` renders a screen the way the respondent sees it: every
question on it editable in place, the one the settings panel describes
marked, one button labelled like the public screen (the last question's
own text, else Next or Submit; a leading message does not lend its
"Continue") and the progress counted in screens. `CanvasQuestion` takes an
optional progress position for forms with screens; its chrome, progress
and button are shared with the new card, unchanged.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
On slides, the Logic canvas draws a dashed frame behind the questions of
each screen, labelled like the spine ("Screen 2 · 3 questions"), redrawn
around a question being dragged, and counted in the canvas size. When the
box would also take in a question that is not on the screen (parallel rows
can put one there), each of its questions gets its own frame instead. The
list view names each screen on its first question. Nothing is drawn on one
page, and nodes and edges stay exactly where they were.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
…s panel

The builder side of screens, on slides only (one page shows none of it and
keeps the ids, so switching back restores the screens):

- Spine: a chain on the boundary above each question joins it to the
  screen above or starts a new screen there, in the grip column. It shows
  on hover and focus (faintly without hover), always while joined, and
  where it cannot join it stays reachable, dimmed, with the reason. The
  rows of a screen close into one block under its name; numbering stays
  per question. The lead-capture nudge knows an email on the last screen.
- Settings: the same boundary as an always visible switch after the Logic
  card, the only way to group below lg where the spine is hidden; hints on
  the type and hidden controls say what they do to a screen; "reveal after"
  on a question of a screen says "after this screen" and inserts the card
  after its last question; the Logic card notes a jump that runs on leaving
  the screen, a target mid-screen or inside its own screen, and a question
  that shows or hides live on its own screen.
- Jumps: the Logic and Branching dialogs, the rule rows and the scheduler's
  after-booking picker offer screen starts only and never the question's
  own screen; a target a rule already holds stays listed, with a note on
  what it does.
- Canvas and state: a question of a screen draws the whole screen, keyed
  by the screen so moving between its questions keeps the card, and a form
  with screens counts progress in screens. Every edit runs `withScreens`,
  so ids stay canonical and, on slides, the partial point never sits
  inside a screen; drags follow the join and leave rules on both layouts.
  `layout` is required wherever it changes what is offered.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
v19-screens gains the builder: the spine and the settings switch join and
split the same boundary, the canvas shows the whole screen and the draft
saves the ids; jump targets are screen starts, with the note in the Logic
card and the dialog, and the preview walks screens; one page hides every
control, keeps the screens, and Design says so.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
…, with its reason

Below lg the settings switch is the only way to group questions, and a
boundary that cannot be joined used a natively disabled switch: out of the
Tab order, with the reason printed under it but not linked to it. It now
stays focusable, is announced as unavailable (`aria-disabled`), is described
by its reason (`aria-describedby`), and a click or a key press does nothing,
as the spine's chain already did. `Switch` and `InlineField` learn to pass
both through; every other caller renders exactly as before. The reasons
live in one place (`screenBlockReason`) for the spine and the switch.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
A jump that already points inside a screen (or inside its own) stays listed
in its picker, so the author sees what it holds. Every picker kept every
rule's target, though: one stale target showed up in the other rules and
in "Always go to", and a new rule started on it when it came first. Each
select now keeps only its own current value, and a new rule starts on the
first target the form actually offers.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
…in it

A hidden file upload is transparent inside a screen, so it can sit between
two of its questions; shown again, it could not rejoin and the canonical
pass cut the screen in two. It now lands right after the screen, as a drag
would put it, with the partial point and the selection following it. A
restored crash backup also goes through the canonical pass now, like every
other change.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
The reason a question type cannot share a screen said "Calendars" and
"calendarios", but the type gallery calls it "Scheduler" and "Agendador",
the names an author looks for. It now reads "Schedulers, reveal screens and
file uploads always get a screen of their own." and "Los agendadores, las
pantallas de revelación y las subidas de archivo siempre van en su propia
pantalla." wherever it shows: the spine's chain, the settings switch and the
type picker's hint.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
… screen

Showing a hidden question again put it back on the screen around it even
when that screen already showed ten, past the builder's cap. It now leaves
the screen and lands right after it, as a drop onto a full screen does; one
short of the cap, it goes back in as before. The hint shown before hiding
still says that hiding takes the question off its screen.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
Nothing can be joined above the first question shown, yet the spine drew a
greyed chain on its top edge, and during a drag on whichever row happened
to be first. The chain is now not drawn at all on a row with no question
shown above it (hidden ones are transparent), and every chain steps aside
while a row is dragged: each boundary is about to change, and they rode
along on rows that moved. The disabled chains with a reason stay
everywhere else, and the settings switch is unchanged.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
The selected row was three accent shapes that did not line up: its border,
an inner rail pill and the title button's square focus outline; inside a
screen the border also sat on the screen's own edge, past the block. A
row's state is now ONE outline drawn inside its border, in the row's shape:
rounded on a question of its own, the screen's corners on a screen's first
and last row, square between, so it never covers the screen's edge. Hover
is a finer version of the same outline, and keyboard focus on the title
shows as it too. The chains get a halo in the card's colour, so the seam
and an outline stop short of them instead of running underneath.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
…he screen name put

Rows of different heights stretched and squashed mid-drag, text included:
dnd-kit's full transform carries a scale for that case. Sortable rows now
move by translation only. The dragged spine row lifts as a rounded card
with a shadow, at full opacity, and the "Screen N" label is no longer part
of its screen's first row, so it stays in place instead of riding along.
The keyboard reorder is unchanged.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
The name stays out of the draggable rows so it never rides along, but mid-drag
the rows slide under it while it holds still, so it sat over rows of another
screen. It now fades out until the drop, like the chains, and keeps its space
so nothing below jumps.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josue Hernandez <josue@daptatech.com>
@josue-commits
josue-commits changed the base branch from feat/screen-groups-engine-renderer to develop September 26, 2026 15:46
@josue-commits
josue-commits merged commit 36022f2 into develop Sep 26, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(renderer): group several questions on one screen in the slides layout

1 participant