Compare commits
No commits in common. "5d8018ef406556c11a6ae808b507ce49acb643d2" and "f4a524ba0bac764485101d1a57ebf53002ffba9f" have entirely different histories.
5d8018ef40
...
f4a524ba0b
5 changed files with 10 additions and 49 deletions
24
CHANGELOG.md
24
CHANGELOG.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue