Skip to content

Show only the questions a submission was actually asked - #326

Merged
EllAchE merged 1 commit into
portal-taxonomy-edit-20260817t1830from
asked-questions-only-20260817t1945
Aug 18, 2026
Merged

Show only the questions a submission was actually asked#326
EllAchE merged 1 commit into
portal-taxonomy-edit-20260817t1830from
asked-questions-only-20260817t1945

Conversation

@EllAchE

@EllAchE EllAchE commented Aug 17, 2026

Copy link
Copy Markdown
Owner

sbek CFP-S2, run 2026-08-17T05-46-05:

Conditional-visibility rules are ignored by every read-only rendering of a submission. "Workshop prerequisites" is configured to show only when Session format is Workshop, and the public submission form honours that correctly in both directions — but the post-submission speaker edit view and the reviewer's Questionnaire panel both render the question on ABS-1, which is a Talk. For a reviewer this is worse than cosmetic: it presents a question that was never asked as one the speaker skipped ("Workshop prerequisites —").

What was wrong

Not the visibility engine — what was handed to it. isFieldVisible shows a field whose condition points at a question missing from the map. That is right for a rule orphaned by a deleted question and wrong for a parent the caller filtered out first. submissionFields dropped the built-ins before resolving, so any rule pointing at the built-in format, track or level was unresolvable and its field showed unconditionally.

The seeded demo does not exhibit this: its only condition (prior_recordinggiven_before) is between two custom fields. The evaluator's form conditioned on the built-in format, which is the case that breaks.

The fix

askedQuestions(fields, row) resolves conditions over the whole form with the built-in columns rehydrated back into answer positions, and leaves dropping the built-ins to the caller — "what was asked" and "what I render" are different questions.

Three surfaces move onto it:

  • Portal editor renders only the questions this submission was asked. A file question is resolved but still not rendered: it is a value another question can be conditioned on, and the portal has nowhere to re-upload from.
  • saveSubmissionAction reads back only those questions. readAnswers writes a key for every field it is given, so the wrongly-shown control was persisting an empty string — afterwards indistinguishable from a speaker who was asked and skipped. updateMySubmission now resolves against the values being written, so a speaker switching a Talk to a Workshop is asked the workshop questions by that same save.
  • Reviewer Questionnaire filters at render (askedAnswers), so rows already carrying an empty answer from the old write path read correctly without a backfill. An answer whose question is gone from the form is kept — there is no rule left to consult, and the panel already falls back to the raw key for those.

Worth knowing

Switching format reveals the newly asked questions on the next load, not live. The format control sits outside FieldSet, so the browser cannot recompute visibility as you change it.

Tests

lib/services/asked-questions.test.ts, 11 cases: gated on the built-in format in both directions, gated on the built-in track, gated on a custom answer, form order preserved; and for askedAnswers — hides an answer never asked (empty or stale), shows one that was, keeps an answer whose question is gone, leaves an unconditional form untouched.

lint, typecheck, test (2065 in 192 files), build, docs:openapi, docs:mcp all clean.

Stacked on #323. Not merged and not deployed.

A question conditioned on "Session format is Workshop" was rendered on a
Talk everywhere a submission is read back: the speaker's portal editor and
the reviewer's Questionnaire panel. The reviewer's copy is the damaging one,
because "Workshop prerequisites —" reads as a question the speaker skipped.

The visibility engine was right; what it was handed was not. `isFieldVisible`
treats a condition whose parent is missing from the map as no condition —
correct for a rule orphaned by a deleted question, wrong for a parent the
caller filtered out first. `submissionFields` dropped the built-ins before
resolving, so every rule pointing at the built-in format, track or level was
unresolvable and its field showed unconditionally.

`askedQuestions` resolves over the whole form, built-in columns rehydrated
back into answer positions, and leaves dropping the built-ins to the caller.

Three surfaces move onto it:

- The portal editor renders only the questions this submission was asked.
  A file question is resolved but still not rendered, because it is a value
  another question can be conditioned on and the portal has nowhere to
  re-upload from.
- `saveSubmissionAction` reads back only those questions. Reading every
  question the form holds stored an empty answer to one never shown, which
  afterwards is indistinguishable from a speaker who was asked and skipped.
  `updateMySubmission` resolves against the values being written, so a
  speaker switching a Talk to a Workshop is asked the workshop questions by
  that same save.
- The reviewer's Questionnaire filters at render, so rows already carrying
  an empty answer from the old write path read correctly without a backfill.
  An answer whose question is gone from the form is kept — there is no rule
  left to consult, and the panel already falls back to the raw key.

Switching format reveals the newly asked questions on the next load: the
format control sits outside `FieldSet`, so the browser cannot recompute
visibility live.

Found by `sbek` CFP-S2 against run 2026-08-17T05-46-05.
@EllAchE
EllAchE merged commit de411da into portal-taxonomy-edit-20260817t1830 Aug 18, 2026
7 checks passed
@EllAchE
EllAchE deleted the asked-questions-only-20260817t1945 branch August 18, 2026 00:06
EllAchE added a commit that referenced this pull request Aug 18, 2026
* Let a speaker fix the format, track and tags they picked

All three were asked on the public form and none survived into the portal's edit
view. Track showed up as read-only header text; format and tags did not show up
at all. A speaker who chose the wrong track had no way to say so, on exactly the
fields organizers filter, route and schedule on -- so the correction went by
email, if it happened.

The edit view now offers the event's own lists: two selects and a checkbox list,
the same three questions the form asked. Values are ids rather than names, so
renaming a track does not detach the submissions that chose it, and the server
action checks each one against the same list rather than trusting the post.

A form that does not ask one of these renders no control for it, and a save from
that form now leaves the column alone rather than clearing it. Tags are replaced
as a set, the way a submit replaces them; an empty set means untagged, and no
`tagIds` at all means the caller was not editing tags.

* Show only the questions a submission was actually asked (#326)

A question conditioned on "Session format is Workshop" was rendered on a
Talk everywhere a submission is read back: the speaker's portal editor and
the reviewer's Questionnaire panel. The reviewer's copy is the damaging one,
because "Workshop prerequisites —" reads as a question the speaker skipped.

The visibility engine was right; what it was handed was not. `isFieldVisible`
treats a condition whose parent is missing from the map as no condition —
correct for a rule orphaned by a deleted question, wrong for a parent the
caller filtered out first. `submissionFields` dropped the built-ins before
resolving, so every rule pointing at the built-in format, track or level was
unresolvable and its field showed unconditionally.

`askedQuestions` resolves over the whole form, built-in columns rehydrated
back into answer positions, and leaves dropping the built-ins to the caller.

Three surfaces move onto it:

- The portal editor renders only the questions this submission was asked.
  A file question is resolved but still not rendered, because it is a value
  another question can be conditioned on and the portal has nowhere to
  re-upload from.
- `saveSubmissionAction` reads back only those questions. Reading every
  question the form holds stored an empty answer to one never shown, which
  afterwards is indistinguishable from a speaker who was asked and skipped.
  `updateMySubmission` resolves against the values being written, so a
  speaker switching a Talk to a Workshop is asked the workshop questions by
  that same save.
- The reviewer's Questionnaire filters at render, so rows already carrying
  an empty answer from the old write path read correctly without a backfill.
  An answer whose question is gone from the form is kept — there is no rule
  left to consult, and the panel already falls back to the raw key.

Switching format reveals the newly asked questions on the next load: the
format control sits outside `FieldSet`, so the browser cannot recompute
visibility live.

Found by `sbek` CFP-S2 against run 2026-08-17T05-46-05.
EllAchE added a commit that referenced this pull request Aug 18, 2026
…322)

* Give the portal back the dropdown the CFP asked Audience level with

A speaker picks Beginner, Intermediate or Advanced on the public form. Opening
the same submission in the portal to edit it presented Audience level as an
empty text box: any string at all could be saved into the column the review
queue filters on and the exports group by, and nothing in the product would ever
match it again.

The edit view now renders the options the form itself offers, read from the form
rather than hardcoded, so an organizer who replaced the list gets their list. A
form whose organizer removed the field offers nothing rather than an empty box.

Server-side too. The edit view posts to a server action, and a server action
takes whatever it is given, so the value is checked against the same list there.
Two exceptions, both because the old box let anything through: a blank clears
the field, which is what an optional question means, and whatever the record
already says passes unchanged -- a speaker fixing a typo in their title should
not be blocked by a value they did not touch and could not re-enter. Moving off
such a value is one-way; the select offers it so it is visible, and choosing
anything else is final.

* Leave the level alone on a form that does not ask for one

The select only renders when the form has the field, so an empty `level` on a
form without it means nobody was shown the question -- not that a speaker
cleared it. Writing it through dropped an existing value on the next save of
anything else on the page.

* Let a speaker fix the format, track and tags they picked (#323)

* Let a speaker fix the format, track and tags they picked

All three were asked on the public form and none survived into the portal's edit
view. Track showed up as read-only header text; format and tags did not show up
at all. A speaker who chose the wrong track had no way to say so, on exactly the
fields organizers filter, route and schedule on -- so the correction went by
email, if it happened.

The edit view now offers the event's own lists: two selects and a checkbox list,
the same three questions the form asked. Values are ids rather than names, so
renaming a track does not detach the submissions that chose it, and the server
action checks each one against the same list rather than trusting the post.

A form that does not ask one of these renders no control for it, and a save from
that form now leaves the column alone rather than clearing it. Tags are replaced
as a set, the way a submit replaces them; an empty set means untagged, and no
`tagIds` at all means the caller was not editing tags.

* Show only the questions a submission was actually asked (#326)

A question conditioned on "Session format is Workshop" was rendered on a
Talk everywhere a submission is read back: the speaker's portal editor and
the reviewer's Questionnaire panel. The reviewer's copy is the damaging one,
because "Workshop prerequisites —" reads as a question the speaker skipped.

The visibility engine was right; what it was handed was not. `isFieldVisible`
treats a condition whose parent is missing from the map as no condition —
correct for a rule orphaned by a deleted question, wrong for a parent the
caller filtered out first. `submissionFields` dropped the built-ins before
resolving, so every rule pointing at the built-in format, track or level was
unresolvable and its field showed unconditionally.

`askedQuestions` resolves over the whole form, built-in columns rehydrated
back into answer positions, and leaves dropping the built-ins to the caller.

Three surfaces move onto it:

- The portal editor renders only the questions this submission was asked.
  A file question is resolved but still not rendered, because it is a value
  another question can be conditioned on and the portal has nowhere to
  re-upload from.
- `saveSubmissionAction` reads back only those questions. Reading every
  question the form holds stored an empty answer to one never shown, which
  afterwards is indistinguishable from a speaker who was asked and skipped.
  `updateMySubmission` resolves against the values being written, so a
  speaker switching a Talk to a Workshop is asked the workshop questions by
  that same save.
- The reviewer's Questionnaire filters at render, so rows already carrying
  an empty answer from the old write path read correctly without a backfill.
  An answer whose question is gone from the form is kept — there is no rule
  left to consult, and the panel already falls back to the raw key.

Switching format reveals the newly asked questions on the next load: the
format control sits outside `FieldSet`, so the browser cannot recompute
visibility live.

Found by `sbek` CFP-S2 against run 2026-08-17T05-46-05.
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.

1 participant