Compare commits

..

2 commits

Author SHA1 Message Date
5d8018ef40 fix(theme): scope dark media override to auto mode only
Some checks failed
CI / Backend (Python) (push) Failing after 1m32s
CI / Frontend (Vue) (push) Failing after 25s
Mirror / mirror (push) Failing after 7s
Release / release (push) Failing after 6s
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
2026-05-05 14:22:59 -07:00
312631a5d9 fix(resume-optimizer): strip double bullets and markdown formatting in rewrites
Three root causes fixed:
- _section_text_for_prompt: strip existing bullet chars from bullet text before
  adding the prompt's own marker (prevents • • text entering the LLM prompt)
- _reparse_experience_bullets: use + quantifier to strip all leading bullet chars,
  not just the first (handles • • text from LLM output)
- _apply_section_rewrite (summary): run _clean_summary_markup to remove
  markdown * bullets from career_summary before storing in struct

Also adds 'no markdown formatting' to the LLM rewrite prompt CRITICAL RULES.
2026-05-05 14:11:52 -07:00
5 changed files with 49 additions and 10 deletions

View file

@ -9,6 +9,30 @@ 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
### Added

View file

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

View file

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

View file

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

View file

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