Compare commits

..

No commits in common. "5d8018ef406556c11a6ae808b507ce49acb643d2" and "f4a524ba0bac764485101d1a57ebf53002ffba9f" have entirely different histories.

5 changed files with 10 additions and 49 deletions

View file

@ -9,30 +9,6 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
--- ---
## [0.9.3] — 2026-05-05
### Added
- **Editable resume review** — proposed summary and experience bullets in the review modal
are now editable text areas. Edits flow through `apply_review_decisions()` and override
the LLM output in the final resume struct. Preview textarea in Apply Workspace is also
editable, with manual changes preserved through the approve step via `preview_text_override`.
### Fixed
- **Double bullets in resume optimizer**`_section_text_for_prompt` now strips existing
bullet characters before prefixing with `•`, and `_reparse_experience_bullets` uses a
greedy strip regex so `• •` patterns can no longer survive parsing.
- **Asterisk markup in summary** — added `_clean_summary_markup()` to strip LLM-generated
markdown bullet chars (`*`, `-`, etc.) from career summary output; injected no-markdown
rule into the LLM prompt's CRITICAL RULES list.
- **Light theme dark CSS bleed**`peregrine.css` media dark override now scoped to
`:root:not([data-theme])` (auto mode only) instead of `:root:not([data-theme="hacker"])`.
Fixes dark navy `--app-primary-light`/`--app-accent-light` bleeding into light themes
(light, solarized-light, colorblind) on dark-OS machines.
---
## [0.9.2] — 2026-05-02 ## [0.9.2] — 2026-05-02
### Added ### Added

View file

@ -278,8 +278,7 @@ def rewrite_for_ats(
f"3. Only rephrase existing content — replace vague verbs/nouns with the " f"3. Only rephrase existing content — replace vague verbs/nouns with the "
f" ATS-preferred equivalents listed above.\n" f" ATS-preferred equivalents listed above.\n"
f"4. Keep the same number of bullet points in experience entries.\n" f"4. Keep the same number of bullet points in experience entries.\n"
f"5. Do NOT use markdown formatting — no **, __, or * for bullets.\n" f"5. Return ONLY the rewritten section content, no labels or explanation."
f"6. Return ONLY the rewritten section content, no labels or explanation."
f"{voice_note}\n\n" f"{voice_note}\n\n"
f"Original {section} section:\n{original_content}" f"Original {section} section:\n{original_content}"
) )
@ -306,8 +305,7 @@ def _section_text_for_prompt(resume: dict[str, Any], section: str) -> str:
for exp in resume.get("experience", []): for exp in resume.get("experience", []):
lines.append(f"{exp['title']} at {exp['company']} ({exp['start_date']}{exp['end_date']})") lines.append(f"{exp['title']} at {exp['company']} ({exp['start_date']}{exp['end_date']})")
for b in exp.get("bullets", []): for b in exp.get("bullets", []):
clean_b = re.sub(r"^[•\-–—*◦▪▸►\s]+", "", b).strip() lines.append(f"{b}")
lines.append(f"{clean_b}")
return "\n".join(lines) if lines else "(empty)" return "\n".join(lines) if lines else "(empty)"
return "(unsupported section)" return "(unsupported section)"
@ -316,7 +314,7 @@ def _apply_section_rewrite(resume: dict[str, Any], section: str, rewritten: str)
"""Return a new resume dict with the given section replaced by rewritten text.""" """Return a new resume dict with the given section replaced by rewritten text."""
updated = dict(resume) updated = dict(resume)
if section == "summary": if section == "summary":
updated["career_summary"] = _clean_summary_markup(rewritten) updated["career_summary"] = rewritten
elif section == "skills": elif section == "skills":
# LLM returns comma-separated or newline-separated skills # LLM returns comma-separated or newline-separated skills
skills = [s.strip() for s in re.split(r"[,\n•·]+", rewritten) if s.strip()] skills = [s.strip() for s in re.split(r"[,\n•·]+", rewritten) if s.strip()]
@ -328,19 +326,6 @@ def _apply_section_rewrite(resume: dict[str, Any], section: str, rewritten: str)
return updated return updated
def _clean_summary_markup(text: str) -> str:
"""Strip markdown/plain-text bullet markers from career summary lines.
LLMs sometimes format summary content with '* item' or '• item' markdown.
This converts those lines to unmarked text so the summary renders cleanly.
"""
lines = []
for line in text.splitlines():
cleaned = re.sub(r"^[•*\-–—◦▪▸►]\s+", "", line.lstrip())
lines.append(cleaned)
return "\n".join(lines).strip()
def _reparse_experience_bullets( def _reparse_experience_bullets(
original_entries: list[dict], original_entries: list[dict],
rewritten_text: str, rewritten_text: str,
@ -370,9 +355,9 @@ def _reparse_experience_bullets(
chunk = remaining chunk = remaining
bullets = [ bullets = [
re.sub(r"^([•\-–—*◦▪▸►]\s*)+", "", line.strip()).strip() re.sub(r"^[•\-–—*◦▪▸►]\s*", "", line).strip()
for line in chunk.splitlines() for line in chunk.splitlines()
if re.match(r"^\s*[•\-–—*◦▪▸►]", line) if re.match(r"^[•\-–—*◦▪▸►]\s*", line.strip())
] ]
new_entry = dict(entry) new_entry = dict(entry)
new_entry["bullets"] = bullets if bullets else entry["bullets"] new_entry["bullets"] = bullets if bullets else entry["bullets"]

View file

@ -135,8 +135,8 @@ body {
bottom: calc(72px + env(safe-area-inset-bottom)); bottom: calc(72px + env(safe-area-inset-bottom));
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
background: var(--color-surface-raised, #f5f7fc); background: var(--color-surface-raised, #2a3650);
color: var(--color-text, #1a2338); color: var(--color-text, #eaeff8);
padding: 10px 20px; padding: 10px 20px;
border-radius: var(--radius-md, 8px); border-radius: var(--radius-md, 8px);
font-size: 0.9rem; font-size: 0.9rem;

View file

@ -79,7 +79,7 @@ body {
/* ── Dark mode ─────────────────────────────────────── */ /* ── Dark mode ─────────────────────────────────────── */
/* Covers both: OS-level dark preference AND explicit dark theme selection in UI */ /* Covers both: OS-level dark preference AND explicit dark theme selection in UI */
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root:not([data-theme]) { :root:not([data-theme="hacker"]) {
--app-primary: #68A8D8; /* Falcon Blue (dark) — 6.54:1 on #16202e ✅ AA */ --app-primary: #68A8D8; /* Falcon Blue (dark) — 6.54:1 on #16202e ✅ AA */
--app-primary-hover: #7BBDE6; --app-primary-hover: #7BBDE6;
--app-primary-light: #0D1F35; --app-primary-light: #0D1F35;

View file

@ -32,7 +32,7 @@ function dismiss(): void {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
gap: var(--space-2, 8px); gap: var(--space-2, 8px);
background: var(--color-surface, #eaeff8); background: var(--color-surface, #0d1829);
border: 1px solid var(--app-primary, #2B6CB0); border: 1px solid var(--app-primary, #2B6CB0);
border-radius: var(--radius-md, 8px); border-radius: var(--radius-md, 8px);
padding: var(--space-2, 8px) var(--space-3, 12px); padding: var(--space-2, 8px) var(--space-3, 12px);
@ -59,5 +59,5 @@ function dismiss(): void {
line-height: 1; line-height: 1;
} }
.hint-chip__dismiss:hover { color: var(--color-text, #1a2338); } .hint-chip__dismiss:hover { color: var(--color-text, #eaeff8); }
</style> </style>