Skip to content

refactor: externalize section keyword config to sections.config.json - #113

Merged
s-annam merged 2 commits into
mainfrom
gh-110
Jun 17, 2026
Merged

refactor: externalize section keyword config to sections.config.json#113
s-annam merged 2 commits into
mainfrom
gh-110

Conversation

@s-annam

@s-annam s-annam commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Moves the three hard-coded section-keyword structures out of `regex.ts` into a
single editable JSON file (`sections.config.json`) with a typed loader
(`sections.config.ts`). Pure refactor — no behaviour change. Same aliases, same
`SectionName` union, same exports to all existing consumers.

  • `sections.config.json` — canonical source for 8 sections: aliases,
    anchors (L2-ready), `splitLetterNormalizable`, and `anchorFallback`. Version
    field `"1"` for future migrations.
  • `sections.config.ts` — typed loader: imports JSON, validates shape at
    module load, derives `SECTION_KEYWORDS` + `SPLIT_LETTER_NORMALIZABLE_SECTIONS`
    (unchanged public API) + new `SECTION_ANCHORS`/`SECTION_ANCHOR_FALLBACK`
    accessors (unread until L2/L3). Drift guard: `const _drift: Record<SectionName,
    SectionConfig> = config.sections` fails the build if a JSON key is missing from
    the union.
  • `regex.ts` — deletes the three inline structures; imports + re-exports them
    from `sections.config.ts` so `sections.ts`, `markdown-lines.ts`, and
    `extract-fields.ts` import paths are unchanged (zero edits to those files).
  • `tsconfig.app.json` — adds `resolveJsonModule: true` (required for tsc;
    Vite already handled JSON at runtime).

Closes #110

Test plan

  • npm run typecheck clean (34 files, 0 errors)
  • npm run test green (34 test files, 414 tests, 0 failures)
  • Drift guard verified: renaming "summary" key in JSON to "summaryX" produces TS2741 at compile time; reverted
  • Manually verified in npm run dev / npm run preview

Move the three hard-coded section-keyword structures out of regex.ts into
a single editable JSON file (sections.config.json) with a typed loader
(sections.config.ts). No behaviour change — same aliases, same SectionName
union, same exports.

- sections.config.json: canonical source for aliases, anchors,
  splitLetterNormalizable, and anchorFallback (8 sections, version "1")
- sections.config.ts: typed loader; validates shape at module load; derives
  SECTION_KEYWORDS, SPLIT_LETTER_NORMALIZABLE_SECTIONS, and new
  SECTION_ANCHORS / SECTION_ANCHOR_FALLBACK accessors (unread until L2);
  drift guard (_drift assignment) fails the build if a JSON key is missing
  from the SectionName union
- regex.ts: deletes the three inline structures; imports + re-exports them
  from sections.config.ts so all existing import paths are unchanged
- tsconfig.app.json: adds resolveJsonModule: true (required for tsc to
  accept the JSON import; Vite already handled it at runtime)

Resolves #110
Comment thread src/lib/heuristics/sections.config.ts Fixed
Comment thread src/lib/heuristics/sections.config.ts Fixed
SECTION_ANCHORS and SECTION_ANCHOR_FALLBACK had no reader until L2 (#111) /
L3 (#112), so fallow correctly flagged them as dead exports. Remove both;
the JSON `anchors` / `anchorFallback` fields stay (still validated at load
via SectionConfig), and #111/#112 will add each accessor alongside its
consumer so no dead export ships.

Refs #110

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rohithgollapalli

Copy link
Copy Markdown
Collaborator

Review — Approve ✅

Clean, behaviorally faithful refactor. No correctness bugs found.

Correctness — verified equivalent

Checked the two derived structures against the deleted originals:

  • SECTION_KEYWORDS — all 8 sections, identical alias lists, identical key order (summary → experience → … → other). JSON preserves insertion order and Object.fromEntries(Object.entries()) preserves it, so iteration order is unchanged.
  • SPLIT_LETTER_NORMALIZABLE_SECTIONS — the JSON splitLetterNormalizable: true set resolves to {summary, experience, education, projects, certifications}, exactly the original hand-written set (skills correctly stays false, preserving the Letter-spaced section headers ('E XPERIENCE') parse with zero experiences, disabling per-bullet grouping #56 sidebar-bleed guard).

Order-dependence doesn't matter regardless — every alias is globally unique across sections, so both matchSectionHeader (.includes) and normalizeSplitLetterHeaders (keywordToSection map) produce identical results.

Type/build safety — sound

  • Both consumers (regex.ts, markdown-lines.ts) read SECTION_KEYWORDS through the explicit as Array<[SectionName, readonly string[]]> cast, so dropping as const for Record<SectionName, readonly string[]> is compatible.
  • .ts import extension matches the repo-wide convention; allowImportingTsExtensions: true is already set.
  • import rawConfig from "*.json" is correctly gated by the newly-added resolveJsonModule: true. Import/re-export-from-same-module in regex.ts is valid TS. No circular import.
  • Removing the unused SECTION_ANCHORS/SECTION_ANCHOR_FALLBACK accessors was the right call — keeps fallow green and defers the export to the L2/L3 PR that adds the reader.

Minor (non-blocking)

  1. Drift guard is one-directional. const _drift: Record<SectionName, SectionConfig> = rawConfig.sections catches a renamed/deleted key, but an extra JSON key is allowed by structural assignment and would flow into SECTION_KEYWORDS at runtime mislabeled as a SectionName. Low practical risk; worth a one-line comment noting the guard only covers missing keys.
  2. Runtime validate() is partial vs. compile-time — validates aliases/anchors arrays but not the splitLetterNormalizable/anchorFallback booleans (those are enforced at compile time via the _drift assignment). Consistent and fine, just slightly asymmetric.
  3. JSON anchors/anchorFallback are validated/typed but unread until L2 — Head-noun anchor fallback for qualified section headers (closes #108) #111/L3 — Visual-primary boundary detection + name/contact disambiguation #112 — data, not dead exports, as the PR notes.

Before merge

The test-plan manual dev/preview box is unchecked. Given full equivalence + 414 tests green (incl. regex.test.ts + split-letter-header.test.ts which pin section matching), I don't consider it blocking — but ticking it costs nothing.

Solid, well-scoped refactor that sets up the L2/L3 anchor work cleanly.

@s-annam
s-annam merged commit 25ba6a1 into main Jun 17, 2026
2 checks passed
@s-annam

s-annam commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Merged — thanks for the thorough review, @rohithgollapalli. Confirmed the equivalence checks and the #56 sidebar-bleed guard call. Your three minors were all non-blocking so I merged on your approval (dismiss-on-push would've bounced it); filed the drift-guard comment correction (your minor #1) as a follow-up rather than re-trigger a review cycle.

@s-annam
s-annam deleted the gh-110 branch June 17, 2026 19:48
s-annam added a commit that referenced this pull request Jun 17, 2026
Lands L2 + L3 of the section-recognition epic (#109, on merged L1 #110), plus #113-review follow-up #114.

- #114 — sections.config.ts _drift guard comment reworded to its true one-directional nature + runtime validate() extra-key check.
- #111 (L2) — head-noun anchor fallback in matchSectionHeader behind 7 FP guardrails; exports SECTION_ANCHORS + SECTION_ANCHOR_FALLBACKS; skills/other stay off the fallback. Synthetic Chromium fixture added.
- #112 (L3) — visual-primary boundary in splitIntoSections (font-ratio >= 1.2) with name/contact disambiguation; first direct unit coverage for splitIntoSections. allCaps dropped as a net-FP visual signal.

Two-column flattened-header sidebar-noise regression tracked separately as #117.

Closes #108
Closes #111
Closes #112
Closes #114
s-annam added a commit that referenced this pull request Jun 25, 2026
…113)

Moves the three hard-coded section-keyword structures out of regex.ts into a single editable sections.config.json with a typed loader (sections.config.ts). Pure refactor — same aliases, same SectionName union, same exports to all consumers. Unused L2/L3 anchor accessors dropped to keep fallow green.

Resolves #110

Co-authored-by: Rohith Gollapalli <rohithgollapalli@users.noreply.github.com>
s-annam added a commit that referenced this pull request Jun 25, 2026
Lands L2 + L3 of the section-recognition epic (#109, on merged L1 #110), plus #113-review follow-up #114.

- #114 — sections.config.ts _drift guard comment reworded to its true one-directional nature + runtime validate() extra-key check.
- #111 (L2) — head-noun anchor fallback in matchSectionHeader behind 7 FP guardrails; exports SECTION_ANCHORS + SECTION_ANCHOR_FALLBACKS; skills/other stay off the fallback. Synthetic Chromium fixture added.
- #112 (L3) — visual-primary boundary in splitIntoSections (font-ratio >= 1.2) with name/contact disambiguation; first direct unit coverage for splitIntoSections. allCaps dropped as a net-FP visual signal.

Two-column flattened-header sidebar-noise regression tracked separately as #117.

Closes #108
Closes #111
Closes #112
Closes #114
s-annam added a commit that referenced this pull request Jun 28, 2026
…113)

Moves the three hard-coded section-keyword structures out of regex.ts into a single editable sections.config.json with a typed loader (sections.config.ts). Pure refactor — same aliases, same SectionName union, same exports to all consumers. Unused L2/L3 anchor accessors dropped to keep fallow green.

Resolves #110

Co-authored-by: Rohith Gollapalli <rohithgollapalli@users.noreply.github.com>
s-annam added a commit that referenced this pull request Jun 28, 2026
Lands L2 + L3 of the section-recognition epic (#109, on merged L1 #110), plus #113-review follow-up #114.

- #114 — sections.config.ts _drift guard comment reworded to its true one-directional nature + runtime validate() extra-key check.
- #111 (L2) — head-noun anchor fallback in matchSectionHeader behind 7 FP guardrails; exports SECTION_ANCHORS + SECTION_ANCHOR_FALLBACKS; skills/other stay off the fallback. Synthetic Chromium fixture added.
- #112 (L3) — visual-primary boundary in splitIntoSections (font-ratio >= 1.2) with name/contact disambiguation; first direct unit coverage for splitIntoSections. allCaps dropped as a net-FP visual signal.

Two-column flattened-header sidebar-noise regression tracked separately as #117.

Closes #108
Closes #111
Closes #112
Closes #114
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

L1 — Externalize section keyword config to a single file

3 participants