peregrine.css used :root:not([data-theme="hacker"]) in the
prefers-color-scheme:dark block, causing --app-primary-light and
--app-accent-light to resolve to dark navy/brown in every explicit
light theme (light, solarized-light, colorblind) on dark-OS machines.
Changed to :root:not([data-theme]) to match theme.css's pattern,
so the media query only fires in auto mode. Explicit [data-theme="dark"]
block handles the dark-theme-on-light-OS case unchanged.
Also fixed incorrect fallback values in HintChip.vue (#0d1829 → #eaeff8)
and App.vue global toast (#2a3650/#eaeff8 → light-mode values).
Closes: dark elements in light themes on dark-OS machines
Summary and experience bullet fields in the review modal are now
editable textareas. Edited values flow through decisions to
apply_review_decisions(), which uses edited_text/edited_bullets when
the section is accepted. Clearing unwanted LLM-added bullets (empty
lines filtered server-side) addresses the extra-bullets issue.
The preview textarea in the apply workspace is also now editable;
approveResume() passes preview_text_override so manual edits survive
the approve step without re-rendering from struct.
--color-primary in dark mode is a medium-light green (#6ab870); white on green
yields ~2.2:1 contrast (fails WCAG AA 4.5:1 minimum). Using --color-surface
(dark navy in dark mode, near-white in light mode) ensures the text always
contrasts strongly with the primary background regardless of theme.
Also tints banner background with 8% primary via color-mix() so it reads as
visually distinct from the page surface without being loud.
- JobCardStack: expose resetCard() to restore card after a blocked action
- JobReviewView: call resetCard() when approve/reject returns false; prevents
card going blank after demo guard blocks the action
- useApi: add 'demo-blocked' to ApiError union; return truthy error from the
403 interceptor so store callers bail early (no optimistic UI update)
- ApplyView: add HintChip to desktop split-pane layout (was mobile-only)
- HintChip: fix text color — --app-primary-light is near-white in light theme,
causing invisible text; switch to --color-text for cross-theme contrast
- vite.config.ts: support VITE_API_TARGET env var for dev proxy override
- migrations/006: add date_posted, hired_feedback columns and references_ table
(columns existed in live DB but were missing from migration history)
- DemoBanner: commit component and test (were untracked)
- scripts/messaging.py: add logged_at param to create_message; use provided value or fall back to _now_utc()
- dev-api.py: add logged_at: Optional[str] = None to MessageCreateBody
- web/src/stores/messaging.ts: remove logged_at from Omit, add as optional intersection so callers can pass it through
- web/src/components/MessageLogModal.vue: pass logged_at in handleSubmit payload; move @keydown.esc from backdrop to modal-dialog (which holds focus); compute localNow fresh inside watch so it reflects actual open time
- references_ + job_references tables with CREATE + migration
- Full CRUD: GET/POST /api/references, PATCH/DELETE /api/references/:id
- Link/unlink to jobs: POST/DELETE /api/references/:id/link-job/:job_id
- GET /api/references/for-job/:job_id — linked refs with prep/letter drafts
- POST /api/references/:id/prep-email — LLM drafts heads-up email to send
reference before interview; persisted to job_references.prep_email
- POST /api/references/:id/rec-letter — LLM drafts recommendation letter
reference can edit and send on their letterhead (Paid/BYOK tier)
- ReferencesView.vue: add/edit/delete form, tag system (technical/managerial/
character/academic), inline confirm-before-delete
- Route /references + IdentificationIcon nav link
API additions (dev-api.py split across this and next commit):
- /api/jobs/{job_id}/qa GET/PATCH/suggest — Interview Prep answer storage + LLM suggestions
- /api/settings/ui-preference POST — persist streamlit/vue preference to user.yaml
- cancel_task() added to scripts/db.py (per-task cancel for Danger Zone)
Vue / UI:
- AppNav: "⚡ Classic" button to switch back to Streamlit UI (writes cookie + persists to user.yaml)
- ApplyWorkspace: Resume Highlights panel (collapsible skills/domains/keywords with job-match highlighting)
- SettingsView: hide Data tab in cloud mode (showData guard)
- ResumeProfileView: minor improvements
- useApi.ts: error handling improvements
Infra:
- compose.cloud.yml: add api service (uvicorn dev_api running in cloud container)
- docker/web/nginx.conf: proxy /api/* to api service in cloud mode
- README.md: Vue SPA now listed as Free tier feature
#50 Job Review list view — sort + filter controls:
- Sort by best match / newest first / company A-Z (client-side computed)
- Remote-only checkbox filter
- Job count indicator; filters reset on tab switch
- Remote badge on list items
#61 Cover letter generation from approved tab:
- '✨ Draft' button on each approved-list item → /apply/:id
- No extra API call; ApplyWorkspace handles generation from there
#54 Company research modal (all API endpoints already existed):
- CompanyResearchModal.vue: 3-state machine (empty→generating→ready)
polling /research/task every 3s, displays all 7 research sections
(company, leadership, talking points, tech, funding, red flags,
accessibility), copy-to-clipboard for talking points, ↺ Refresh
- InterviewCard: new 'research' emit + '🔍 Research' button for
phone_screen/interviewing/offer stages
- InterviewsView: wires modal with researchJobId/Title/AutoGen state;
auto-opens modal with autoGenerate=true when a job is moved to
phone_screen (mirrors Streamlit behaviour)
ApplyWorkspace.vue: kept HEAD (vue-spa) version for resume optimizer panel,
cl-error__actions wrapper, and ResumeOptimizerPanel import. main's older
version lacked these additions.
- Fix 1: Add missing `:` binding prefix to aria-label on score badge
(was emitting literal backtick template string to DOM)
- Fix 2: Remove unused `watch` import from InterviewPrepView.vue
- Fix 3: guardAndLoad now checks interviewsStore.error after fetchAll;
shows pageError banner instead of silently redirecting to /interviews
on network failure; job is now a ref set explicitly in the guard
- Fix 4: Remove unconditional research-badge from InterviewCard.vue
(added in this branch; card has no access to prep store so badge
always showed regardless of whether research exists)
When a new job prop arrives after approve/reject, the watch cleared the
exit-transform inline style while the spring transition was still active,
causing the card to animate from offscreen back to center before the new
card rendered. Fix: set transition:none before clearing the style, then
restore it on the next rAF — browser paints the new position first.