Release Notes
Version 0.5.2
Changes
- Added
SYNTAX.md, a one-page syntax reference: every keyword with where it is allowed, stored values, condition rules, load-time validation errors, and known limits (features that parse but don't render yet). Itssurveyexamples are parsed bytests/syntax-examples.test.tsso the reference stays in sync with the parser
Bug Fixes
- Fixed answers to hidden questions still steering conditions: changing an earlier answer so that a later, already answered question became hidden (e.g. unticking a checkbox option, or picking a different radio option) left that later answer in the variables, so it kept driving
SHOW_IF,COMPUTE, and{{IF}}/{var}placeholders even though the respondent could no longer see or change it. Variables are now derived only from content that is visible: answers inside a hidden block, page, section, question, or matrix row, and selected options hidden by their ownSHOW_IF, count as unanswered. The answers themselves are kept, so they count again as soon as the content becomes visible. Because visibility depends on the variables, derivation repeats until the result is stable, which resolves chains where hiding one question hides the nextderiveVariablesanduseQuestionnaireResponsesnow take the survey's blocks instead of a flat page list, so block-levelSHOW_IFand computed variables can be evaluated
Internal
- Updated dependencies: Next 16.3.4 → 16.3.6, React 19.2.8 → 19.3.0, lucide-react 1.43 → 1.48, tailwind-merge 3.6 → 3.7, ESLint 10.10 → 10.11, and Vitest 4.1 → 5.0 (no test changes needed). TypeScript stays on 6.0 because
typescript-eslint(viaeslint-config-next) does not support 7 yet
Version 0.5.1
Changes
- Added a GitHub link to the navbar: an icon-only button linking to the project repository, shown on the home, documentation, and releases pages
Bug Fixes
- Fixed checkbox/radio
- TEXToptions corrupting their stored value and breakingSHOW_IF: typed text was encoded directly into the response string (e.g."Other, namely:" + ": " + "painting"), which produced a doubled separator whenever the option's own label already ended in punctuation ("Other, namely:: painting"), and madeSHOW_IF/==comparisons against the bare option value silently stop matching as soon as any text was typed. Typed text is now stored separately from the selected value, soresponses/{variable}always hold the plain option value(s) selected — conditions keep matching regardless of typed text — while{variable AS LIST}/{variable AS INLINE_LIST}and other text placeholders join the option's label with its typed text using a single space ("Other, namely: painting"), so the label's own punctuation controls how they read together instead of the app inventing a separator - Fixed checkbox response order following click order instead of option order: a checkbox question's stored array (and therefore
{variable AS LIST}) reflected the order options were checked in, not the order they're declared in the survey. Selections are now always ordered to match the question's option list
Internal
- Linting works again:
pnpm lintrannext lint, a command Next 16 removed, andeslint-config-nexthad been dropped fromdevDependenciesby an earlier dead-code pass (it is referenced by the ESLint config rather than imported, so the tool could not see the reference). The script now runseslint .,eslint-config-nextis back, andeslint.config.mjsuses its native flat configs directly instead of theFlatCompatshim (@eslint/eslintrcis no longer needed).eslint-plugin-react's React-version autodetection calls an API removed in ESLint 10, so the config pinssettings.react.version;@next/next/no-img-elementis off because markdown images come from arbitrary survey text - Removed
setState-in-effect patterns flagged by the React Compiler lint rules: eight effects that synchronised state React can derive or read directly were rewritten, so no component renders once with a placeholder value and then again with the real one- Page index bounds are clamped during render (
clampPageIndex) instead of corrected afterwards, so an out-of-range index is never rendered when answers hide pages. A respondent whose current page becomes hidden is moved to the last visible page and returns to where they were if those pages become reachable again - Visited-page tracking moved from
RespondentNavigatorintouseQuestionnaireNavigation, which now takes the visible pages and returns the set of page ids reached. The navigator derives visited nav items from it rather than accumulating them in an effect - Expansion state in both navigators is now derived — the page navigator expands the block holding the current page, the respondent navigator expands groups whose pages have been reached — with manual open/close choices remembered until the next navigation, matching the previous reset-on-navigate behaviour
localStoragepreferences (language, navigation settings) are read throughuseSyncExternalStore(hooks/use-local-storage.ts) so the stored value is applied as part of hydration, and the survey draft editor reads its draft in a lazyuseStateinitialiser since it only mounts after the user opens it- Mac detection for the keyboard-shortcut hint reads
navigatorthrough the same store pattern instead of a mount effect
- Page index bounds are clamped during render (
- Switched the package manager to pnpm:
package-lock.jsonis replaced bypnpm-lock.yaml,package.jsonpinspackageManager, and the npm-onlyoverridesfield moved tooverridesin a newpnpm-workspace.yaml(pnpm 11 no longer reads settings frompackage.json). Re-resolving the dependency tree from the version ranges also picked up newer patch/minor releases (Next 16.2.9 → 16.3.4, React 19.2.7 → 19.2.8, lucide-react 1.17 → 1.43, TypeScript 6.0.3, ESLint 10.10, Vitest 4.1.11) - TEXTother-text is now lifted intouseQuestionnaireResponses(anotherTextsmap alongsideresponses) instead of living in localuseStateinsidecheckbox-question.tsx/radio-question.tsx. A new pureapplyOtherText(lib/response-variables.ts) composes it into a separatedisplayVariablesobject for text rendering, whilevariables(used forSHOW_IF/expressions) stays plain.displayVariables/otherTextsare threaded alongside the existingvariables/responsesprops throughpage-content.tsx→section-renderer.tsx→question-renderer.tsx→ the question components; removed the now-redundant colon-parsing duplicated incheckbox-question.tsx,radio-question.tsx, andlib/utils/tab-index-calculator.ts
Version 0.5.0
Changes
-
Rewritten condition engine:
SHOW_IFandCOMPUTEexpressions are now evaluated by a real expression parser instead of string splitting. What this changes for surveys:- Parentheses work:
SHOW_IF: (age >= 65 OR age < 30) AND consent == Yesnow groups correctly (previously it silently mis-evaluated) - Keywords are UPPERCASE-only:
AND,OR,NOT,IS,IS_NOT,STARTS_WITHetc. are only recognized in uppercase, so unquoted answer values containing words like "or" ("Several weeks or more") are no longer torn apart. Lowercaseand/or/notare now treated as plain text NOTbinds tighter thanAND/OR:NOT a AND bnow means(NOT a) AND b(conventional precedence); previously it negated everything after it. No surveys or examples in this repository used the old interpretation- String comparisons fixed: comparing two text variables (
name1 == name2) now compares their text; previously both sides were coerced to numbers, making any two non-numeric values "equal" STARTS_WITHaccepts quoted values:STARTS_WITH crime == "Yes"now works the same as the unquoted form- No more JavaScript evaluation: arithmetic is evaluated by the parser itself instead of generated JavaScript code, so survey text can never execute code
- Parentheses work:
-
Malformed conditions are now rejected when a survey is loaded: every
SHOW_IFandCOMPUTEexpression — on blocks, pages, sections, questions, options, and matrix subquestions — is syntax-checked at parse time, and errors name the exact location (e.g.Question "Q3" option "Sometimes" SHOW_IF "mode == (": Unbalanced parentheses). Previously a broken condition silently kept its content visible; surveys that relied on that will now fail to load until the condition is fixed. The runtime evaluator keeps a fail-safe default (content stays visible plus a console warning) as a last resort for conditions that slip past validation -
Variable validation is more thorough: duplicate variable names are now caught across all variable-defining locations — question, matrix subquestion, breakdown option, and computed variables — not just question-level
VARIABLE:.SHOW_IFundefined-variable checks now also cover sections and matrix subquestions, matching the coverage questions and options already had -
EXCLUSIVEcheckbox options: A checkbox option can now be marked- EXCLUSIVE(indented under the option, like- TEXT) so selecting it deselects every other selected option, and selecting any other option deselects it. Useful for options like "None of the above". A question can have more than one exclusive option; selecting one always deselects the others. Exclusive options render with a circular, radio-style indicator to signal this to respondents -
Multi-line
Q:text: Question text can now span multiple lines using"""delimiters, the same convention already used byHINT:,REVEAL:, andTOOLTIP:. This lets a question reference a bulleted or numbered list of examples as part of its own text (e.g. "has your employer provided any of the following measures: ...") without those list items being mistaken for answer options- Example:
Q: """on its own line, the markdown content (including a-or*list), then a closing"""on its own line, followed by the actual options
- Example:
-
Summed comparisons in
COMPUTE:: A computed variable can now add up multiple comparisons, e.g.COMPUTE: hazard_total = heat == Yes + cold == Yes + severe == Yes, counting 1 for each matching term. Plain numbers can be mixed into the sum, and the result can be used like any other computed variable (placeholders,SHOW_IF) -
TOOLTIP:is now a popover,REVEAL:is the old inline panel: The previousTOOLTIP:keyword toggled an inline info panel below the element; that behavior is nowREVEAL:.TOOLTIP:instead shows a small popover next to the text on click, for shorter contextual hints. Both are supported on pages, sections, questions, all option types (including multiple-choice and checkbox), and matrix subquestions, and can be combined- Existing surveys using
TOOLTIP:for inline panels should switch toREVEAL:to keep the same behavior
- Existing surveys using
-
HINT:,TOOLTIP:, andREVEAL:now render on multiple-choice and checkbox options: these were already parsed on every option type but silently ignored for anything other than breakdown options. A- HINT:/- TOOLTIP:/- REVEAL:indented under a-option now shows the same muted subtext, popover, and collapsible panel as elsewhere -
Tooltip and reveal icons now sit consistently next to the text everywhere: pages, sections, questions, breakdown/matrix/multiple-choice/checkbox options previously used two different layouts — a fixed left-margin icon for
REVEAL:and an icon trailing the text forTOOLTIP:— which drifted apart once a label wrapped to multiple lines (the left-margin icon centers on the whole block; text-trailing follows the last line). Both icons now trail the text everywhere, in the same order (tooltip, then reveal), and are sized to fit inside a line of text instead of overhanging it. The page no longer reserves a left icon gutter (pl-8), since nothing is positioned there anymore -
Empty checkbox variables and list conjunctions are now localized:
{selections}on an unanswered checkbox variable rendered as the English word "none" and{colors AS INLINE_LIST}always joined the last item with "and", regardless of the survey's language setting. Both now follow the current language (Dutch: "geen"/"en") -
Block-level computed variables are now global: A
COMPUTE:defined at block level is visible everywhere in the survey, not just within its own block- Block-level computeds can reference each other across blocks (resolved by dependency order)
- A
SHOW_IF:on any block, page, section, or question can now reference a computed defined in any other block - Page-level computeds remain page-scoped, seeded with the global block-level set
- Names must be unique across blocks — defining the same compute name in two different blocks is now a validation error
- Multiple
COMPUTE:statements for the same name within a single block are still allowed (the default-then-override pattern)
-
Fixed documentation sidebar scrolling: The sidebar now scrolls independently on long pages instead of overflowing the viewport
-
Updated dependencies: Bumped all packages to latest versions; added a postcss override to resolve a XSS vulnerability (GHSA-qx2v-qp2m-jg93)
Bug Fixes
-
Fixed page navigator block visibility: The page navigator was re-evaluating block
SHOW_IFconditions using the current page's computed variables instead of the global computed variables, which could cause it to show a different visibility state than the actual survey navigation. The navigator now derives block visibility directly from the viewer's own computation. -
Hidden pages no longer clutter the nav sidebar:
NAVIGATIONitems were built once at parse time and shown unconditionally, so a page excluded bySHOW_IFstill appeared in the respondent's sidebar (permanently greyed out). Nav items are now filtered by current page visibility, and reappear automatically once the respondent's answers make the page reachable again. A visible level-2 item whose level-1 parent is hidden is promoted to a top-level entry so it isn't lost from the nav. -
Fixed dash-bulleted lists inside
"""blocks being parsed as answer options: A-line inside a delimitedHINT:,REVEAL:, orTOOLTIP:block (e.g. a bullet list of examples) was being scraped by the option parser as a real, selectable option in addition to being rendered as text. Delimited content is now correctly excluded from option and question-type detection, so-and*lists inside any"""block render as plain lists and never become options -
Fixed checkbox options rendering as circles: All checkbox inputs, not just
EXCLUSIVEones, were rendering as full circles instead of rounded squares. The checkbox's corner radius used the theme's--radius-lg(10px), which exceeds half the checkbox's 16px size — the browser clamps that to a perfect circle regardless of the intended shape. Non-exclusive checkboxes now use a fixed 4px radius so they render as squares again;EXCLUSIVEoptions are unaffected and still render as circles -
Fixed matrix tables being too wide with few response options: The row-label column had a fixed 25% width, so with
table-fixedlayout the remaining 75% was split evenly across however many response-option columns existed — a 2-option matrix (e.g. Yes/No) ended up with very wide, mostly-empty columns. Response-option columns now use a fixed width instead, and the row-label column takes up the remaining space -
Fixed question type detection rejecting options starting with "Q": A question whose options all began with the letter "Q" (e.g. "Quality", "Quantity") was parsed as a plain text question with no options — the option-detection check excluded any dash line starting with "Q" in an attempt to skip matrix subquestion markers (
- Q1: ...). Option detection now shares the same logic used to actually build the option list, so only real subquestion markers and metadata lines are excluded -
Fixed section
REVEAL:content being silently dropped: Section-levelREVEAL:text was parsed correctly but never reached the page — the visibility-filtering step rebuilt each section object without copying therevealfield over. The reveal button and panel are also no longer restricted to sections that have a title -
Fixed bare page-level
REVEAL:/TOOLTIP:swallowing later keywords: When a page usedREVEAL:orTOOLTIP:with no text on the same line, anyNAVIGATION:,COMPUTE:, orSHOW_IF:line that immediately followed was absorbed into the reveal/tooltip text instead of being processed — silently losing the page's nav level, computed variable, or visibility condition -
Fixed the bundled sample survey: quoted the
experienced_userCOMPUTE comparison value and fixed a typo, so "Load Sample Survey" shows its intended conditional block -
Fixed breakdown option
SHOW_IFbeing silently ignored:- SHOW_IF:on a breakdown row was parsed and validated but never affected rendering — every row always showed regardless of the condition. Hidden rows are now excluded from the table and from totals/subtotals; their stored value is kept and restored if the row becomes visible again -
Fixed order-dependent variable derivation: variables were derived by walking responses in the order the respondent answered them, not the order questions appear in the survey. A breakdown row's
VALUE:placeholder referencing another question's variable could resolve differently (or not at all) depending on the order the two questions were answered, e.g. via back-navigation. Variables are now derived by walking the questionnaire's own page/section/question order, so identical answers always produce identical variables -
Question-level
VARIABLE:on a matrix question is now a parse error: matrix responses are stored per row, so a bareVARIABLE:at the question level (as opposed to- VARIABLE:under a row) was silently accepted but never populated — any condition, placeholder, or computed variable depending on it just silently never resolved. This now fails to load with a message pointing to the per-row- VARIABLE:syntax -
Fixed forward-referencing
CUSTOM:breakdown subtotals: aCUSTOM:subtotal formula could only resolve another subtotal's variable if that subtotal appeared above it in the option list — the value was written into the lookup map row-by-row as the table rendered, so a reference to a subtotal defined further down silently failed. Subtotals are now precomputed as a single, order-independent pass before anyCUSTOM:formula is evaluated, so aCUSTOM:row can reference any other subtotal's variable regardless of table position. This also fixes the same latent issue in variable derivation (lib/response-variables.ts), which shared the identical row-by-row logic
Internal
- Reduced code duplication across
lib/parser.ts,lib/validation.ts, andcomponents/questions/breakdown-question.tsx(~245 lines removed) - Consolidated variable-definition collection in
lib/validation.tsinto a singlecollectVariableDefinitionshelper, reused by both the name-uniqueness and reference validators - Extracted a shared
BaseOptiontype for the fieldsOptionandBreakdownOptionactually have in common (value,label,hint,reveal,tooltip,showIf), and fixed type-narrowing errors inparser-option-exclusive.test.ts/parser-option-text.test.tssurfaced bytsc --noEmit - Extracted breakdown row visibility/summing into
lib/breakdown-calculations.tsand the two-pass variable derivation out ofuse-questionnaire-responses.tsinto a pure, directly-testablelib/response-variables.ts; the hook is now a thinuseStatewrapper around it - Extracted breakdown subtotal computation (
computeSubtotalValues/subtotalVariables) intolib/breakdown-calculations.ts, shared bybreakdown-question.tsxandlib/response-variables.tsinstead of being duplicated - Removed render-time mutation of shared parsed state:
evaluateComputedValuesno longer writes back ontoComputedVariable.value(the field is deleted from the type; nothing read it) — computed values are only ever returned, never stored on the parsed questionnaire - Deduplicated parser state machines in
lib/parser.ts: apushCurrentOptionhelper replaces the triplicated option-push block inparseOptions; sharedbeginMetadataCollection/stepMetadataCollectionhelpers (single-line/bare/"""-delimited, parameterized by a terminator predicate) replace the near-identicalREVEAL/TOOLTIP/SHOW_IFstate machines duplicated acrossparsePageandparseSection - Extracted
breakdown-question.tsx's option label rendering intocomponents/questions/shared/option-label-content.tsx, shared byradio-question.tsxandcheckbox-question.tsx - Removed the gutter/absolute-positioning path for
REVEAL:icons (question-header.tsx,page-header.tsx,section-renderer.tsx) in favor of the same text-trailing layout used by tooltips and options; deleted the now-unusedpl-8/-ml-8gutter compensation fromquestionnaire-viewer.tsxandcomponents/ui/table.tsx
Version 0.4.0
Released May 2026
New Features
-
Conditional string values in COMPUTE: Computed variables can now return strings conditionally
IF <condition> THEN <value> ELSE <value>:COMPUTE: label = IF score >= 8 THEN "High" ELSE "Low"ELSE IFchaining:COMPUTE: label = IF score >= 8 THEN "High" ELSE IF score >= 5 THEN "Medium" ELSE "Low"- One-sided
IF <condition> THEN <value>(noELSE): leaves the variable unchanged if the condition is false, enabling a default-then-override pattern with multipleCOMPUTEstatements for the same variable - Values can be quoted strings, numbers, or references to other variables
- The condition uses the same syntax as
SHOW_IF - String results can be used in text placeholders (
{label}) and inSHOW_IFcomparisons
-
String literal COMPUTE values: Assign a fixed string to a computed variable with
COMPUTE: category = "high" -
Single-line breaks in text: Single newlines in text content now render as line breaks, making it possible to place text on new lines without creating a new paragraph
Improvements
- Package updates: Updated all dependencies to their latest versions
Bug Fixes
- Breakdown option variables recognized in SHOW_IF: Variables defined on breakdown question options (via
- VARIABLE:) are now correctly recognized during validation, so referencing them inSHOW_IFconditions no longer produces a false "undefined variable" error
Removed
- Static survey pages: Removed all hardcoded survey routes (e.g.
/r1-groothandel,/r8-bouw) along with their API routes and source files — these were added to share URLs that immediately loaded a specific survey, but are no longer needed
Version 0.3.1
Released January 2026
New Features
-
Section SHOW_IF support: Sections can now be conditionally shown or hidden based on variables and responses
- Uses the same
SHOW_IF:syntax as pages and questions - Hidden sections and their questions are excluded from page completion tracking
- Example:
SHOW_IF: is_manager == Yeson a##section heading
- Uses the same
-
Section TOOLTIP documentation: Section-level tooltips are now documented with interactive examples
- The tooltip rendering was already implemented but undocumented — an info icon appears next to the section heading and toggles the tooltip content
- Uses the same
TOOLTIP:syntax as pages and questions (including multi-line"""delimiters)
-
Page Navigator documentation: Added dedicated documentation page explaining the Page Navigator feature
- Covers overview, how to open it, and its features (page status indicators, jump-to-page, completion tracking)
Breaking Changes
- Removed
- ESSAYand- OTHERoption modifiers: Only- TEXTis now supported for adding text inputs to options- ESSAYand- OTHERwere functionally identical to- TEXTand have been removed to simplify the syntax- Surveys using
- ESSAYor- OTHERon options should replace them with- TEXT
Improvements
-
URL-based documentation routing: Each documentation section now has its own URL
- Refreshing the page stays on the current section instead of resetting to Overview
- Sections are bookmarkable and shareable (e.g.,
/docs/matrix,/docs/conditionals) - Implemented as static Next.js routes with a shared layout
-
Polished documentation
- Went over the documentation pages and added clarifications where needed, improved consistency between pages, and improved styling
-
Documentation language override: Documentation examples now always render in English regardless of the user's language setting
- Added
defaultLanguageprop toLanguageProviderto force a specific language - Prevents Dutch placeholders and button labels from appearing in documentation
- Added
-
Expanded Overview page: Added detailed introductory text explaining what TST does and its design philosophy
-
Collapsible navigation sections: The chevron on expandable navigation items is now an independent click target
- Clicking the chevron toggles expand/collapse without navigating
- Clicking the item text still navigates (and auto-expands if collapsed)
- Previously, expanded sections could not be collapsed
Bug fixes
- Documentation state bug: Fixed a bug where navigation between pages in one documentation example affected the navigation state in another documentation example
- Added
key={activeSection}to force React to remount examples when navigating between sections - Previously, navigating in one section's example could affect examples on other pages
- Added
Version 0.3.0
Released December 2025
Major Changes
-
Parser refactor: Complete rewrite using chunk-based hierarchical architecture
- Reduced parser code from ~2600 lines to ~1100 lines (60% reduction)
- Hierarchical parsing: Questionnaire → Blocks/NavItems → Pages → Sections → Questions
- Type-specific parsers for all 7 question types
- Eliminated indentation-based parsing - dash markers now identify metadata
- Improved maintainability and extensibility
-
Single-pass parsing architecture: Refactored
parseSection,parsePage, andparseBlockto use state machines- Fixed bug where multi-line delimited tooltip content was rendered twice (as metadata and as content)
- Each parser uses specific state names describing exactly what's being parsed (e.g.,
'tooltip' | 'showif' | 'content'instead of vague'normal') - Structural markers (
#,##,BLOCK:) extracted immediately before entering state machine parseSection: Single-pass state machine extracts section metadata (TOOLTIP, SHOW_IF) and builds content/questions in one loopparsePage: Single-pass state machine extracts page metadata (NAVIGATION, TOOLTIP, COMPUTE) before building sections inlineparseBlock: Single-pass state machine extracts block metadata (SHOW_IF, COMPUTE) before building pages inline- Removed unused helper functions:
identifySections,identifyPages,parseComputedVariables - Benefits: No duplicate content bugs, no keyword leakage between levels, more efficient parsing, explicit state-driven logic
-
Multi-line hints and tooltips for options: Fixed option-level delimited content parsing
parseOptionsandparseBreakdownOptionsnow handle multi-line HINT/TOOLTIP content correctly- Delimited content collection moved outside the
-prefix check to capture all lines - Supports option-level syntax:
- HINT: """followed by multi-line content then""" identifyQuestionsupdated to skip delimited keyword blocks when identifying question boundaries
-
CUSTOM keyword support: Added missing parser support for custom subtotal calculations
CUSTOM:keyword now recognized as breakdown option metadata- Enables custom expressions for SUBTOTAL calculations:
CUSTOM: {{salary + bonus}} - Previously treated as separate option row; now correctly parsed as SUBTOTAL metadata
-
Consistent TOTAL styling: Removed hardcoded styling from TOTAL rows
- Removed
font-bold/font-semiboldclasses from TOTAL row rendering - Added Markdown rendering to TOTAL labels for user-controlled styling
- TOTAL labels now support Markdown formatting like SUBTOTAL labels:
**Total Revenue** - Removed redundant
hover:bg-transparentclasses from all TableRow elements
- Removed
-
Changed to more formal Dutch placeholders
- Changed "Voer je antwoord in..." to "Voer uw antwoord in...
Breaking Changes
-
Delimiter syntax change: Replaced triple dashes (
---) with triple quotes (""") for multi-line content- Reason: Triple dashes conflict with Markdown setext heading syntax, causing Prettier to corrupt survey files
- Old syntax:
HINT: ---followed by content then--- - New syntax:
HINT: """followed by content then""" - Impact: All multi-line HINT and TOOLTIP blocks must be updated
- Migration: Use find-and-replace to change
---on delimiter lines to""" - Benefit: Survey
.mdfiles can now be safely formatted with Prettier without corruption
-
Navigation syntax change: Replaced
NAV:+LEVEL:with singleNAVIGATION:keyword- Old syntax:
NAV: Section Namefollowed byLEVEL: 1 - New syntax:
NAVIGATION: 1placed after page title (page title becomes navigation label) - Navigation is now page-based metadata instead of separate structure
- Pages have unique IDs for reliable navigation highlighting
- Single source of truth - no page duplication between BLOCKS and navigation
- Old syntax:
New Features
-
Navigation settings: Added setting to control whether respondents can jump to unvisited pages
- New toggle in Settings: "Allow Navigation to Unvisited Pages"
- Default behavior: respondents can only navigate to visited or current pages
- When enabled: respondents can jump to any page in the navigation sidebar
- Setting persists in localStorage across sessions
-
SEPARATOR support for breakdown questions: Added missing parser support for separator rows
- SEPARATORkeyword now recognized in breakdown questions- Creates empty rows for visual organization between option groups
- Example: Place separator between different cost categories
- Separator rows are excluded from calculations automatically
-
Improved computed value display in breakdown questions: Placeholder shown when values aren't available
- When CUSTOM calculations reference unavailable variables, shows
–instead of€\{variable\}.000,- - When VALUE (prefillValue) uses unavailable variables, shows
–instead of escaped placeholders - Once variables become available, calculated values display normally with proper formatting
- Applies to both SUBTOTAL rows with CUSTOM and regular options with VALUE
- When CUSTOM calculations reference unavailable variables, shows
-
Interleaved text and questions: Text can now appear between questions in natural flow
-
Questions end at blank lines, allowing content to be interspersed with questions
-
Section structure changed to ordered
itemsarray (content and question items) -
Example:
Q: First question? TEXT Here's some explanatory text between questions. Q: Second question? NUMBER
-
Bug Fixes
-
Fixed duplicate question IDs across blocks: Question IDs are now unique across all blocks
- Bug introduced in 0.3.0 parser refactor: each block reset the question ID counter, causing questions in different blocks to have duplicate IDs (Q1, Q1, Q2 instead of Q1, Q2, Q3)
- This caused block-level SHOW_IF conditions to fail because variable derivation would pick up the wrong question's variable
- Added test suite to prevent regression
-
Fixed paragraph spacing in text content: Text with blank lines between paragraphs now renders correctly
- Fixed parser bug where blank lines between content paragraphs were incorrectly skipped
- Removed redundant
questionLineSetcheck that was marking blank lines as question lines - Added paragraph spacing CSS (
mb-4) to properly space separate paragraphs - Added test suite for paragraph spacing with 4 test cases
-
Restored
- TEXToption modifier: This option-level modifier was lost during parser refactor- TEXTsets theallowsOtherTextflag on options- Allows respondents to provide free text input when selecting specific options
- Works with both multiple choice and checkbox questions
- Added test suite for option text inputs
Improvements
-
Markdown-based text formatting: Removed
whitespace-pre-wrapCSS and let Markdown handle paragraph breaks naturally- Removed
whitespace-pre-wrapfrom all text rendering components (section text, question hints/tooltips, page headers) - Added global CSS rules for paragraph and list spacing (
mb-3withlast:mb-0) - Content now uses standard Markdown convention: double newlines create separate paragraphs with proper spacing
- Benefits: cleaner code, consistent spacing, proper semantic HTML, no CSS overrides needed
- Removed redundant wrapper divs that only held the CSS class
- Removed
-
Parser maintainability: Extracted helper functions to reduce duplication
- Added
parseComputedVariables()helper - eliminated duplication betweenparsePage()andparseBlock() - Added
createOption()helper - reduced boilerplate in option creation across 4 locations - Reduced parser by 28+ lines while maintaining readability
- Added
-
Type system cleanup: Removed redundant and confusing types
- Removed
VisiblePageContentwrapper type - now useSection[]directly - Removed
MatrixOptiontype - matrix questions now use standardOptiontype - Removed unused
Subquestionfields:subtract,subtotalLabel,value - Renamed
ComputedVariables→ComputedValuesfor clarity (map of values, not array of definitions) - Removed
ParsedQuestiontype that duplicated theQuestiondiscriminated union
- Removed
-
Simplified parser data structures: Removed unnecessary object wrappers and dead code
- Eliminated
Chunkwrapper type - chunks are now juststring[]instead of{ lines: string[] } - Removed
Linewrapper type - lines are now plain strings instead of{ line: string, index: number } - Removed unused
index,startIndex, andendIndexfields that were assigned but never read - Eliminated
shouldParseflag that was tracked throughout the entire parsing pipeline - Reduced parser complexity by ~60 lines of unnecessary object creation and field tracking
- Code fences in section content are preserved naturally for markdown rendering
- No functional changes - parser works exactly the same way with simpler data structures
- Eliminated
-
Section titles now display: Section headings (after
##) are now rendered as visible h2 elements- Section
contentfield is now optional (no empty strings stored) - Updated documentation examples to demonstrate section usage
- Fixed section content rendering bug where page titles appeared as section content
- Section
Documentation
-
Improved documentation page design and readability:
- Simplified Overview example to showcase markdown-like text format without BLOCKS or NAVIGATION
- Unified navbar component across all pages with proper width matching
- Improved typography: larger font sizes for primary content, muted colors reserved for secondary content only
- Consistent Usage section styling: all sections now use bullet points instead of mixed patterns
- Added visual styling for inline code elements (monospace, background, padding, rounded corners)
- Removed "Documentation" header from sidebar for cleaner design
- Fixed Overview item styling in sidebar to match other items
- Updated Questions section description to be more concrete
- Added comprehensive documentation system guidelines to CLAUDE.md
-
Improved documentation UX:
- Reversed example order: code appears first, then rendered result (follows standard technical documentation pattern)
- Fixed auto-scroll bug: documentation examples no longer cause page to scroll to top when navigating between survey pages
- Removed outdated text-format-guide component reference from CLAUDE.md
-
Added 7 standard example files demonstrating text format features:
- basic-survey.md - Common question types
- conditional-logic.md - SHOW_IF demonstrations
- matrix-questions.md - Matrix question variations
- multi-page.md - Multi-page survey with sections
- breakdown-budget.md - Basic breakdown with computed variables
- breakdown-advanced.md - Advanced breakdown features
- range-syntax.md - RANGE syntax examples
Version 0.2.2
Released December 2025
New Features
- Added SHOW_IF support for matrix subquestions - rows can now be conditionally shown/hidden based on responses
- Added RANGE syntax for generating numeric options (e.g.,
RANGE: 1-10creates options 1, 2, 3, ..., 10) - Redesigned documentation navigation with survey-style card layout
- Added dedicated
/surveyroute for better browser back button behavior
Improvements
- Browser back button now properly returns to home page from surveys
- Reduced shadow on navigation sidebars from
shadow-smtoshadow-xsfor more subtle styling- Navigation shadows now match input element styling for consistency
Breaking Changes
- Subquestion VARIABLE syntax changed: Use
- VARIABLE:(with dash) instead ofVARIABLE:(without dash)- This makes all subquestion modifiers consistent with dash prefix:
- HINT:,- TOOLTIP:,- VARIABLE:,- SHOW_IF:
- This makes all subquestion modifiers consistent with dash prefix:
Bug Fixes
- Fixed matrix question parser bug where questions with subquestions were incorrectly classified as
multiple_choiceinstead ofmatrixtype
Testing
- Added Vitest testing framework
- Added comprehensive parser test suite with 13 tests covering all question types
- Tests validate correct question type detection and conditional subquestion behavior
Version 0.2.1
Released December 2025
Bug Fixes
- Fixed React key uniqueness warnings by using array indices instead of option values as keys across all question types
- Fixed matrix question parsing bug where questions with subquestions were incorrectly classified as multiple_choice instead of matrix
Improvements
- Removed hardcoded bold styling from SUBTOTAL rows - users can now control formatting via Markdown
- Added comprehensive BREAKDOWN documentation covering all features
Removed
- Removed TOTAL_COLUMN feature - use EXCLUDE on individual rows instead for better flexibility
Version 0.2.0
Released December 2025
New Features
BREAKDOWN Question Type
Added a new question type for collecting numeric data in table format with automatic totals and subtotals.
- Automatic total calculation across all options
- Support for subtotal rows with automatic or custom calculations
- Multi-column layouts with column-specific totals
- Header rows and separator rows for visual organization
- Question and option-level PREFIX and SUFFIX to add units (e.g., currency information)
- Read-only calculated values using VALUE keyword
- VARIABLE support for storing individual row values (including totals)
- EXCLUDE keyword to display options without including in totals
- SUBTRACT keyword for deductions in calculations
TOOLTIP Support
Added tooltips for contextual help.
- Tooltips work at page-, section-, question-, and option-level tooltips on page titles
- Multi-line tooltip support with delimiter syntax (TOOLTIP: ---)
Documentation
Added a dedicated documentation page.
- Organized by topic: Survey Structure, Question Types, Dynamic Features, Customization
- Live interactive previews for every feature
- Side-by-side code and rendered output
- Sidebar with nested navigation
Text Editor with Auto-Save
Added a text editor to draft surveys directly in the app with automatic localStorage persistence.
Improvements
Improved multi-line HINT text
- HINT text can now reliably span multiple lines using triple dashes (---) as a delimiter
Navigation
- Navigation visibility now defaults to on with "Hide Navigation" toggle
- Improved parent navigation items - now clickable to jump to first child page
- More stable hover and active states
UI/UX
- Improved section spacing for better visual hierarchy
Settings
- Default language changed to Dutch (nl) to match typical survey content
Developer Experience
- Implemented discriminated union types for better type safety
Removed
- Removed navigation validation that prevented surveys without explicit NAV declarations
Version 0.1.0
Initial Release
Core Features
Text-Based Survey Creation
Create surveys using a simple text format that gets parsed into an interactive questionnaire.
Question Types
- Multiple choice (radio buttons)
- Checkbox (multi-select)
- Text input (single line)
- Essay (multi-line textarea)
- Number input
- Matrix questions (table layout with rows and columns)
Survey Structure
- Multi-page surveys with navigation
- Sections for organizing questions
- Blocks for grouping pages
Dynamic Features
- Variables for storing and referencing responses
- Conditional logic (SHOW_IF) for dynamic surveys
- Conditional text for dynamic question wording
- Computed variables with arithmetic expressions
- STARTS_WITH operator for testing multiple variables
Customization
- Hints for additional question context
- Markdown formatting support
- List formatting (bullet lists and inline lists)
File Handling
- Upload .txt or .md files
- Drag and drop support
Help
- Load Sample Survey
- Text Syntax Guide
Settings
- Language selection (Dutch/English)
- Navigation visibility toggle
- Navigation position (left/right)