v2.0.6 Survey construction
Version 2.0.6 Survey JS construction
Survey Question Prefix Index
current draft document
Reference index of every question-name prefix/convention used in this
project's SurveyJS task forms — what it's for, who can see/edit it, and how
that's enforced. Pulled together from praxis-consolidated-playbook.md,
this session's MQL/LLn work, and the real TaskClient.tsx. Status per
entry: Confirmed (seen live or in real code), Designed (agreed, not
yet built), Placeholder (reserved, no implementation), or Open
(unresolved as of this index).
1. Hidden/system fields — never rendered as questions
All live in a hidden base_panel, injected/overridden by TaskClient.tsx
on load — these are the security boundary; stored survey data can't
override them.
| Field | Holds | Notes |
|---|---|---|
studentID | Student's ID | Set by whichever role's first save creates the record |
moduleID | Module context | See "Fixed" vs "Floating" module assignment in the consolidated playbook |
superID | Supervisor's ID once claimed | Empty = no supervisor yet. Drives SQ*/Q* cascade |
markerID | Marker's ID once claimed | Empty = no marker yet. Drives MQ* cascade |
filesID | Comma-separated key:uid pairs | e.g. "UL1:4183AAE7-...,UL2:..." — plain string, not JSON despite an old debug stub in TaskClient.tsx assuming otherwise |
altHide | 3-state (null/1/2) | Placement Review only — controls new-Q/SQ/MQ visibility for that mid/end-year twin-record flow. Purely display, no effect on routing/state |
observer | Observer's ID | No visibleIf/enableIf cascade defined yet — observer role exists in TaskClient.tsx (isObserver) but has no confirmed question-visibility convention of its own |
2. Role-owned answer prefixes
Confirmed — standard three-role set, praxis-consolidated-playbook.md:
| Prefix | Role | Visibility | Editability |
|---|---|---|---|
Q1, Q2, ... | Student | Always visible | enableIf: "{superID} empty" (JSON-authored) |
SQ1, SQ2, ... | Supervisor | visibleIf: "{superID} notempty" | enableIf: "{markerID} empty" (JSON-authored) |
MQ1 (exact) | Marker — public | isMarker || (isStudent && task.state === "marked") — code-enforced, not JSON visibleIf | Marker-only |
MQ2, MQ3, ... | Marker — private | isMarker only, at any task.state — code-enforced | Marker-only |
MQ1's public/private split and the switch to code-enforced visibility (rather than trusting each question's ownvisibleIf) is this session's design, superseding the older documented convention that allMQ*visibility ran entirely off{markerID} notemptyin the JSON.- Generic editability rule (this session, code-enforced): each role
edits only its own prefix; every other role gets
readOnly, including roles that own no prefix at all (observer, and the futuretutor). Not locked bytask.stateforMQ*— deliberately left open-ended (transient internal commentary). SQ*/MQ*visibility has no relationship totask.state—stateonly drivesTaskClient.tsx's own UI locking (upload buttons, autosave, survey read-only mode), confirmed directly from real save-route code.
Confirmed — YCW NOS criteria (session-status-260720.md,
marker-save integration (2026-07-21).md):
| Prefix | Meaning | Notes |
|---|---|---|
NQ1, NQ2, ... | YW-course rating | Scores 1–3; 0 = not-assessed (seed default) |
NC1, NC2, ... | YW-course comment | Optional |
QQ1, QQ2, ... | SP-course rating | Renamed from SQ/SC to avoid colliding with the real Supervisor prefix |
QC1, QC2, ... | SP-course comment | Optional |
- Write to two destinations, both confirmed as of 2026-07-25:
staffJSON.markeron theSTrecord itself (so the survey form correctly re-populates these fields on reload), and a separate table (NOScriteria, one row per student, keyed bystudentID) — extracted inline duringmarker-save, non-fatally (aNOScriteriawrite failure never blocks the actual marker save). Originally documented (2026-07-21) as writing toNOScriteriaonly — corrected once thestaffJSON.markerround-trip gap was found. - Fix history — this took three attempts across two sessions to actually
land, not one:
extractMarkerData()inTaskClient.tsxoriginally (client-side) only forwardedMQ*+markerID, silently strippingNQ*/QQ*/NC*/QC*before they ever reached the server — identified 2026-07-21, but the fix wasn't actually deployed, confirmed by a live test on 2026-07-25 that still showed the gap. That same test-and-trace also found the identical bug in two further, previously-undocumented locations:marker-save/route.ts's ownmarkerAnswersfilter (server-side write), and[rowId]/route.ts'sextractStaffSections()fallback filter (server-side read-back) — both also only keptMQ*. All three are now fixed with the same widened five-prefix list, confirmed via a real save → reload round-trip. Full account:marker-save-integration-2026-07-21.md's 2026-07-25 session update. - Open:
NOScriteria's student-key field name is assumedstudentID, not independently confirmed.
Reserved (this session):
| Prefix | Meaning | Notes |
|---|---|---|
MQL (exact) | Never a visible question | Marker-only. Presence triggers the auto-loading Inspera score panel — see the MQL playbook. Not a "role-owned answer," a pure trigger/carrier |
3. Upload prefixes and their companions
Confirmed, live in TaskClient.tsx's UploadKind union — type fixed
by prefix at survey-authoring time, discovered via s.getAllQuestions()
- regex match on
type === "text", thenq.visible = falseto hide the built-in widget in favour of custom upload UI:
| Prefix | Kind | Pipeline | Status |
|---|---|---|---|
UL1, UL2, ... | Main document | Extraction service (/extract/direct) | Confirmed. Was .docx, now PDF (2026-07-23 decision) |
FL1, FL2, ... | Supporting file | Same extraction pipeline as UL* | Confirmed, always PDF |
PL1, PL2, ... | PDF, stored only | No extraction | Present in code's upload-kind map; backend implementation status not independently verified in this index |
AL1, AL2, ... (bare AL = instance 1) | Audio | Not implemented | Placeholder only — no pipeline decided. Paired C{n} content field is reserved for its future transcript, see note below |
ML1, ML2, ... | Images, multi-file | Contact-sheet endpoint, own component (MultiImageUpload) | Confirmed, but see caveat below |
ML's trailing digit means something different from every other kind: forUL/FL/AL/PLthe digit is an instance number (UL1,UL2= two separate questions). ForML, the digit is the max files allowed (ML5= one question, cap of 5 files) — confirmed safe only because there's ever oneMLquestion per survey; a secondMLquestion would break this reinterpretation.DL(pptx) is aspirational, not implemented — mentioned only in a code comment as a future kind "extracted like UL." It is not in the actualUploadKindtype union and has no entry in the style map. Don't confuse this withDL0*below — same two letters, unrelated purpose.- Content field
C{n}— now vestigial, not actively used. Reserved for future use, tied toAL*. Each upload questionX{n}still pairs with a hidden content fieldC{n}(e.g.UL1↔C1) in the code (contentFieldName), andC\d+still appears in the server-side save/submit allow-lists. But its original purpose — carrying extracted text inline — was already skipped fordocx/pdf/pptxuploads even before this session (SKIP_CONTENT_FIELD_TYPESinTaskClient.tsx), with extracted text living onTaskDocuments.extractedTextserver-side instead. Now thatUL*/FL*/PL*are all PDF-only (2026-07-23 decision), every currently-implemented document upload kind falls into that skip list — there's no remaining upload type that actually populatesC{n}today. Its only remaining live purpose is paired withAL*(audio, still Placeholder — no pipeline decided) — a future audio transcript is the one case left whereC{n}would actually get written to again. Left in place structurally (field mapping, allow-list entries) for exactly that reason, not dead code to remove.
Video — confirmed, separate from the document pipeline entirely:
| Prefix | Meaning |
|---|---|
VL1, VL2, ... | Video upload — Bunny.net TUS, async move to S3-compatible storage. Fields live directly on the ST record (videoID, videoStatus, videoEmbedUrl, videoFilename, unsuffixed for VL1; video2ID etc. for VL2+, not confirmed, only inferred by analogy) |
V1 (exact, type: "html") | Companion display link for VL1's result — HTML interpolates {videoURL}. Visibility: visibleIf: "{videoURL} notempty", testing the field, not V1's own value |
A1 | Stub — same companion-display role anticipated for future AL* audio, mirroring V1. Placeholder only |
4. Display-only link prefixes
| Prefix | Meaning | Status |
| --- | --- | --- ß|
| DL01, DL02, ... | Static download link for a template file the student downloads and re-uploads (paired with a UL*), hardcoded at survey-authoring time | Confirmed convention. Not every UL*/VL* question needs a paired DL0* — plain evidence-upload tasks can have none |
| WL1 | Dynamic download link for the renamed uploaded filename | visibleIf: "{XX1} notempty" — no role restriction, visible to everyone. Populated server-side after the FTP-renamed-file save completes, independent of the survey's own save cycle |
| LL1, LL2, LL3, LL4, ... | Resolvable view/download links for already-uploaded files (video, main file, supporting files, future audio), for supervisor + marker | Designed, not built. See the MQL playbook §8 for full detail. Visibility: "{superID} notempty} or {markerID} notempty" (author-controlled plain visibleIf, not code-enforced like MQ*). Resolves via its own filesID key (LL1:<uid>), reverse-direction of UL1's uid — points at a read-facing lookup, not a patch target. Still open: which record/table the filename/URL actually lives on |
5. Special-purpose / non-prefix carriers
| Field | Meaning | Status |
|---|---|---|
XX1 | Hidden carrier — never written back from the survey form itself; written server-side by the upload/video pipeline right after the FTP-renamed-file save completes. Lives in staffJSON.all — a third sub-key alongside super/marker, for content owned by no single role's save action, survives student/supervisor/marker saves automatically via existing pass-through/allow-list logic | Confirmed |
R1 | Whole-task resubmission — single question holding the prior task's student answers + marker feedback, condensed into one plain-text entry (not a per-question mirror). Excluded from aggregatedLog so it isn't double-counted | Confirmed, scope currently believed Flow-B-specific, not yet confirmed general |
6. Role editability — generic rule (this session)
Applies across Q*/SQ*/MQ*, code-enforced, ignoring whatever the
survey JSON's own visibleIf/readOnly says:
| Prefix | Owning role | Everyone else |
|---|---|---|
Q<n> | student | readOnly |
SQ<n> | supervisor | readOnly |
MQ<n> | marker | readOnly |
| (no prefix) | — | observer and the future tutor role — always readOnly, on everything |
tutordoesn't exist as a role yet inTaskClient.tsx(student/supervisor/marker/observeronly). It's planned as a broader version ofobserver— its own tutor-to-student relationship model will define which students/placements it can see, replacing theplacement > supervisorflagobserveruses today. That model is deferred; the only settled behaviour is thattutor, once it exists, never edits anything, same asobserver.- This is client-side defense in depth, not the real security
boundary — the server-side allow-lists in
save/route.ts/submit/route.ts(Q*/UL\d+/C\d+only) are what actually stop a role from persisting a value it shouldn't.
7. Open items across this whole index
observer's own question-visibility convention — never defined; only the hiddenobserverfield exists.PL*'s backend implementation status — present in the upload-kind code, not independently confirmed as a live server-side pipeline.DL(pptx upload) — comment-only, not a real implemented kind.VL2+'s field-suffix convention (video2IDetc.) — inferred by analogy, never directly confirmed.NOScriteria's student-key field name — assumedstudentID.LLn— filename/URL storage location entirely undecided; see theMQLplaybook §8 for the current state of that design.