Skip to content

feat(marking): print stylesheet + .json session save/load (nightly-build threshold)#61

Open
hyperpolymath wants to merge 1 commit into
feature/composer-rubric-editorfrom
feature/composer-print-and-json-io
Open

feat(marking): print stylesheet + .json session save/load (nightly-build threshold)#61
hyperpolymath wants to merge 1 commit into
feature/composer-rubric-editorfrom
feature/composer-print-and-json-io

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Stacked PR (9 of 9 follow-ups). Base = `feature/composer-rubric-editor` (PR #59). Merge order: #51#52#53#54#55#56#57#58#59 → this. PR #60 (finch lock) can land independently and in any order — it only affects deps.

Summary

Pushes the composer to the threshold of "first nightly build": output is no longer just two textareas to copy-paste — it can be printed cleanly and saved/restored as a portable .json artefact.

Print

  • New `@media print` block in `assets/css/app.css` with composer-specific classes (`composer-print-hide` / `composer-print-page`) so the interactive form, header and aside are hidden in print mode, and a dedicated paper-friendly layout takes their place.
  • Print page shows:
    • Student name + band + rubric summary + generation timestamp
    • The manual-override warning (when present)
    • Both prose boxes as paragraph-flowed text
    • Per-component breakdown as a table
    • A borderline-review list if any flags fired
  • New Print button next to "Clear / new student" — uses a JS hook + `window.print()`.

Session save/load

  • Save session (.json) bundles current `form_state` + outputs (`did_well`, `improve`, `band`) and triggers a browser download. Filename: `exam-feedback[-Student]-.json`.
  • Open session (.json) opens a hidden file input; selecting a file pushes `restore_session` to the server, which normalises `form_state` through the existing `normalize_form_params` path (so a tampered file is re-validated) and restores the cached outputs.
  • Schema:
    ```json
    {
    "schema_version": 1,
    "saved_at": "2026-06-10T07:42:00Z",
    "form_state": { /* full normalised form params */ },
    "outputs": {
    "did_well": "...",
    "improve": "...",
    "band": {"key": "good", "label": "good / Pass 2", "mark": 72}
    }
    }
    ```
  • Atom safety: `map_to_band/1` uses `String.to_existing_atom` + `rescue` so a malformed band key cannot poison the BEAM atom table.

What this closes

With PRs #51#59 + #60 (deps) + this:

  • Composer renders, persists across refresh (localStorage), saves/loads as portable file, prints cleanly, exposes rubric editor + reload.
  • The "save / print / load" foundations called out in the previous turn are all wired.

What this does not close

  • Browser walkthrough still pending — verified at module level only (68 tests). The `mix phx.server` workflow needs the `finch` bump (chore(deps): bump finch 0.20 -> 0.22 to satisfy req 0.5.18 #60) + a working local OTP install with `syntax_tools` (separate, environment-dependent).
  • TMA-pipeline integration (FHI ingestion / .docx export / CubDB) — separate Phase 1 work, weeks of effort.

Test plan

  • LiveView parses cleanly
  • 68 composer module tests still pass
  • Browser: Generate feedback → Save session → reopen tab → Open session → form + outputs restored
  • Browser: Print → preview shows the dedicated print page (paper-style); interactive chrome hidden
  • Atom-safety: open a session JSON with a bogus `band.key` → load completes, band shown as nil rather than crashing
  • Save with no student name → filename falls back to `exam-feedback-.json`

Generated with Claude Code.

Pushes the composer to the threshold of "first nightly build":
output is no longer just two textareas to copy-paste — it can be
printed cleanly and saved/restored as a portable .json artifact.

Print
  - New @media print block in assets/css/app.css with composer-specific
    classes (composer-print-hide / composer-print-page) so the
    interactive form, header and aside are hidden in print mode and a
    dedicated, paper-friendly layout takes their place
  - Print page shows: student name + band + rubric summary + generation
    timestamp; the manual-override warning when present; both prose
    boxes as paragraph-flowed text; per-component breakdown table; and
    a borderline-review list if any flags fired
  - New "Print" button next to "Clear / new student" — uses a JS hook
    + window.print()

Session save/load
  - "Save session (.json)" button bundles current form_state + outputs
    (did_well, improve, band) and triggers a browser download via the
    DownloadSession JS hook. Filename: exam-feedback[-Student]-<ISO8601>.json
  - "Open session (.json)" button opens a hidden file input via the
    OpenSession hook; selecting a file pushes "restore_session" to the
    server, which normalises the form_state through the existing
    normalize_form_params path (so a tampered file is re-validated)
    and restores the cached outputs
  - JSON schema: {schema_version, saved_at, form_state, outputs:
    {did_well, improve, band:{key,label,mark}}}
  - Atom safety: map_to_band/1 uses String.to_existing_atom + rescue
    so a malformed band key cannot poison the BEAM atom table

LiveView
  - 3 new buttons in the form actions row (Save / Open / Print)
  - Hidden <input type=file id=open-session-input> with phx-hook
  - 4 new private helpers: band_to_map, map_to_band, build_filename,
    plus the 3 new handle_event clauses (download_session,
    restore_session, open_session)

JS
  - Hooks.DownloadSession, Hooks.OpenSession, Hooks.PrintTrigger
  - All silent-on-failure (no JS exception breaks the page)

Tests (68 still pass — the new wiring is LiveView-only; helpers are
trivial map round-trips. Browser walkthrough still pending.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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