— RELEASE NOTES —

Changelog

All notable changes to Rubric will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Added

  • Default on-disk export folder. Interview storage no longer has to be configured before you get browsable files: when no Storage path is set, Rubric now defaults to a Rubric folder in your Documents and writes each generated/edited interview there as interview.yaml / interviewer-guide.md / candidate-board.excalidraw. This is purely a convenience second copy — the durable in-app store (below) already guarantees nothing is lost regardless of the Storage setting. Settings → Storage shows the default path and you can still point it at any folder or git repo.

Changed

  • Candidate boards now present the scenario, requirements, and design-focus criteria instead of the interview timeline (the timeline stays in the interviewer guide).

Fixed

  • Generated and edited interviews now survive a restart. Previously the library loaded only the build-time bundle, so an interview you generated or edited at runtime lived in memory only — a crash, quit, or auto-update silently dropped it (and with no Settings → Storage path configured, it was unrecoverable). Generated/edited interviews now persist to a durable, app-managed store (a Hive box, the same layer sessions and reports use) that is merged with the bundle on every launch, independent of the optional external Storage path. An edit to a bundled interview replaces it in place; the external Storage backend remains a bonus export/git target on top of this.
  • A half-written Hive box no longer prevents the app from launching, and a failed bootstrap shows the reason instead of a blank screen. Hive's built-in crash recovery truncates a box left half-written by a hard kill (e.g. force-quit mid report-export) back to its last valid frame, so the app starts normally. If a box still can't be opened — most often because a second copy of Rubric is already running and holds the data lock — Rubric shows a clear message ("Rubric may already be running… your data is safe") instead of a blank screen or crash. It never deletes or resets your data to recover.
  • Generated candidate boards no longer render garbled characters: non-ASCII punctuation is transliterated to ASCII for the Excalidraw hand-drawn font.
  • Generated candidate-board content is auto-fitted to its boxes (text is wrapped and panels/boxes grow and relayout), so content no longer overflows.

[0.2.0] - 2026-06-11

Added

  • Interview transcription & diarization: optionally record a session, see best-effort live captions while recording, and get a speaker-attributed transcript (Interviewer vs. Candidate) when you stop. The diarized transcript is stored with the session and fed to the report writer and reviewer as grounding evidence, so observations and quotes trace back to what was said. Transcription uses OpenAI's gpt-4o-transcribe-diarize (Anthropic has no speech-to-text API), with its own key independent of the report provider; a one-time voice enrollment lets the model label the interviewer. New Settings → Voice & transcription section (OpenAI key, enable toggle, keep-audio toggle, voice enrollment), a Record/Stop control + live-caption strip on the session screen, and a renameable diarized transcript with a retry affordance on the report screen. macOS/Windows only; macOS gains a microphone entitlement + usage string (additive — the Sparkle/non-sandbox contract is unchanged).
  • AI request resilience: transient AI failures no longer kill an in-flight report or interview generation. A new ResilientAIService decorator wraps every provider (Anthropic, OpenAI, Ollama) and retries on 429 rate-limits and 502/503/504 gateway errors with exponential backoff (3 attempts, ±25% jitter, honoring the Retry-After header when Anthropic sends one). Streaming retries are pre-yield only — once any chunk has been emitted to the consumer the partial response is preserved and the error propagates. Typed AIException subtypes (AIRateLimitException, AIServerException, AINetworkException, AITimeoutException, AICancelledException, AIClientException) replace bare DioExceptions so the UI can render actionable, classified failure copy.
  • Stop button for in-flight AI calls: the Generate, Revise, and Report screens each render a Stop affordance while an AI call is running. The cancellation flows through Dio's CancelToken on AIRequest; the writer↔reviewer loops also check the token between turns so they exit cleanly without committing a partial draft. Stopped generations surface a neutral "Generation stopped" snackbar rather than an error banner.
  • Per-call AI timeouts: a new AITimeouts class centralises the connect (10s), send (30s), blocking-receive (60s), and streaming-receive (3min) defaults applied to every provider's Dio client. Streaming endpoints override the receive timeout per request via AITimeouts.streamingRequestOptions(). Tuned in one place — not surfaced in Settings.
  • Branded dark DMG installer background: dark #1A1A1A canvas with a dot-grid texture, red left-edge accent stripe, faint oversized "R" watermark, corner brackets, and "RUBRIC" wordmark. Generated by app/scripts/generate_dmg_background.py (Pillow) and committed to app/assets/dmg/dmg_background.png. Regenerate at any time with make dmg-bg.
  • Auto-update (macOS): the app now checks for new versions on launch and offers a one-click in-place install via Sparkle through the auto_updater plugin. A new Updates section in Settings exposes the "Check for updates automatically" toggle, a manual "Check now" button, and the timestamp of the last successful check; a standard Check for Updates… item also lives under the application menu. Releases are signed with an EdDSA key in CI and announced through a Sparkle appcast at /appcast.xml.

Changed

  • AI failure messaging: snackbars on the Generate / Revise / Report screens now surface the typed AIException.userMessage (e.g. "AI provider is rate-limited — retry in 7s.", "AI authentication failed. Check your API key in Settings.") instead of a raw stack trace. The fallback "Generation failed: $e" path remains for unclassified errors.
  • Release entitlements: the Release.entitlements no longer requests com.apple.security.app-sandbox. Rubric is distributed under Developer ID (not Mac App Store), and the hardened runtime + notarization are unchanged — dropping the sandbox lets Sparkle replace the running bundle without a custom XPC service.

[0.1.2] - 2026-05-15

[0.1.1] - 2026-05-15

Added

  • macOS downloads: signed, notarized, and stapled Rubric.dmg installer, distributed as a GitHub Release and reachable from the Download page on the website. Built by a manually-triggered GitHub Actions workflow that also bumps the version and rolls the changelog as a single step.
  • Interview Review Agent: every AI-generated interview is now validated by a second LLM agent against best-practice / intent-alignment / target-alignment criteria. The generator and reviewer run a self-correcting loop (up to 3 turns) — each NEEDS_REVISION verdict feeds the reviewer's suggestedFix list back into the reviser for the next draft. The chosen verdict (PASS / NEEDS_REVISION / FAIL) plus per-finding details are surfaced on the Revise screen and a "REVIEWED" chip appears on the Library card.
  • Report Review Agent + shared EVALUATION_CRITERIA.md: generated reports are now fact-checked against the underlying session evidence (rubric scores, timestamped notes, qualitative fields, hiring decision). Both the report writer and the reviewer reference the same evaluation criteria — score-to-language calibration, forbidden patterns, per-section grounding rules — so the model stops over-claiming on candidates whose scores and notes don't support the praise. The writer↔reviewer loop runs up to 3 turns; the verdict, grounding score, and findings persist on InterviewReport and surface as a collapsible Reviewer Notes panel beneath the rendered report. Reviewer notes never enter PDF / markdown exports — they live only in the app UI.
  • Report → Edit and Refine with AI: every report now has a top-bar Edit toggle that swaps the rendered paper for a markdown source editor (Save persists, Cancel discards) plus a "Refine with AI" input at the top of the report pane. Each reviewer finding also gets an "Apply rewrite" button that feeds the reviewer's suggestedRewrite directly into the writer; after refine the reviewer re-runs once and the new verdict + findings replace the panel.
  • Library → Edit: every interview in the Library now has an Edit button that opens a three-tab editor for interviewer-guide.md (markdown editor with live preview toggle), interview.yaml (structured form for metadata + rubric — typed fields, dropdowns for difficulty/cloud/author, chip lists for tags and target roles, expandable rubric category cards with descriptors 1–5), and candidate-board.excalidraw (the embedded Excalidraw board, round-tripped on save). Save validates by re-parsing the regenerated YAML through InterviewParser, updates the in-memory library, and writes through to the configured storage backend; storage failures surface a snackbar but never block the in-app edit. Slug and category are immutable in this version.
  • OpenAI as a third AI provider option in Settings (alongside Anthropic and Ollama). Uses the Chat Completions API over direct Dio HTTP, with gpt-4o as the default model.
  • Report → Export PDF: previously a "coming soon" stub, now renders an editorial-styled PDF (Source Serif 4 body, rubrum scoring, marginalia recommendation, drop-cap executive summary) and opens the system print/save dialog cross-platform.
  • Report → Send: previously a "coming soon" stub, now renders the report to a temporary PDF and opens the OS share sheet (Mail / Messages / Slack / AirDrop on macOS via share_plus). Falls back to sharing the markdown if PDF rendering fails.
  • Library load warnings: a marginalia-styled, dismissible banner appears at the top of the Library screen when the bundled library_bundle.json is missing or one or more interviews fail to parse — replacing the previous silent skip.
  • Report and PDF export now include a visual dot scorecard (per-category score indicators) between the recommendation and summary sections.
  • Board snapshot (zoom-to-fit PNG) captured at session completion is shown below the report paper on-screen and embedded at the end of the PDF export.

Changed

  • Storage write failures during interview generation now surface a snackbar with the underlying error (previously swallowed silently). Affects local filesystem, git, and S3 backends.
  • Theming: replaced six hard-coded hex colors in the report screen and primary-button hover state with new Tokens constants (inkHover, shimmerBase, shimmerHighlight, elevationPaper) so all surfaces honor the design system.

Fixed

  • Report screen: long final reports now render from the full markdown body across as many on-screen paper pages as needed, instead of dropping generated sections that did not fit the parsed summary view.

Changed

  • Live Session interviewer-guide panel now splits the guide into one tab per ## section (Interview Structure, Scoring Rubric, Follow-Up Questions, etc.) instead of one long scroll. Tabs render in a horizontal carousel with chevron arrows when they overflow the panel width; left/right arrow keys also cycle sections when the strip is focused. Robust against malformed LLM output: missing H1, headings inside code fences, duplicate section titles, inline formatting in headings, and guides with no headings at all all render coherently.
  • Session autosave failures are now logged via debugPrint instead of being silently dropped — affects every score / note / candidate-info edit on the Live Session screen. The mutators stay non-blocking; only the error path changes.
  • AI streaming providers (Anthropic / OpenAI / Ollama) now log malformed SSE / JSON Lines events instead of silently swallowing them, so intermittent provider protocol drift is visible during development. The streams still continue past the bad event.
  • LegacyRubricParser drops rows whose 1/3/5 descriptors are empty (would render as a blank scoring tile) and deduplicates colliding category IDs by appending a numeric suffix — both with debugPrint so the regression is visible. Previously empty descriptors silently produced unusable categories and duplicate IDs silently dropped scores.

[0.1.0] - 2026-05-07

Added

  • Library: browse, search, and filter a curated catalog of 30-minute system design interviews by category, difficulty, and tag.
  • Live Session: a four-panel workspace combining the candidate whiteboard, the private interviewer guide, the rubric scorecard, and a timestamped notes pane.
  • Scoring: weighted rubrics with 0–5 descriptors per category and a final score that updates in real time as you score.
  • Reports: AI-authored interview reports (Anthropic or OpenAI) generated from session scores and notes, with a hire / no-hire / follow-up recommendation, in-app editing, and PDF or Markdown export.
  • Generate: create new interviews from a role / app-type / difficulty prompt — produces the candidate whiteboard, interviewer guide, and weighted rubric in one step.
  • Storage: choice of local filesystem, git repository, or AWS S3 for generated interviews and saved reports.
  • Settings: provider selection (Anthropic or OpenAI), API key, model override, and default storage backend.
  • In-app user guide reachable from a help icon on every screen.
  • Public website with user guide, changelog, downloads, and license pages.
  • MIT license, distributed alongside every install.