Skip to content

Control descriptions: full IOF coverage, smarter autodetect, print/editor sheet parity - #27

Merged
ellmkay merged 1 commit into
mainfrom
feature/control-description-fixup
Sep 24, 2026
Merged

ellmkay merged 1 commit into
mainfrom
feature/control-description-fixup

Conversation

@ellmkay

@ellmkay ellmkay commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Brings control descriptions to full IOF 2018/2024 coverage and fixes the field-testing findings that followed.

Data model & sheet

  • ControlDescription gains e (appearance / second feature) and h (other information). Dimensions move from column E to F per the spec. One shared descriptionCells() replaces two duplicated OCAD→IOF converters.
  • New courses.description_instructions JSONB (migration 20260924180000_course_description_instructions) for special-instruction rows (13.x) and the finish variant (14.x).
  • Shared buildDescriptionSheet() row model: 3-row IOF header (event / classes / course · length · climb), start row, controls, specials, finish with the measured last-control → finish distance. Used by the on-map sheet, the print block and the layout-editor preview, so screen and print are identical.
  • IOF layout rules: bold header with thick cell rules; thick rule under start, after every third description, on either side of a special instruction, above the finish; thick verticals after columns C and F (A B C | D E F | G H); alternate row shading.

Editor

  • Reworked ControlDescriptionEditor: labelled 3×3 compass grid for column C, visible symbol names (mobile-friendly), E/H sections, live summary sentence, bottom sheet on narrow screens. en/sv complete.
  • Crosshair cursor in editor mode + magnifier loupe while dragging (MapLoupe over passive TileLayers sharing the blob cache).
  • Context-menu actions are disabled rather than silently ignored while a save is in flight / before the control list has loaded.

Autodetect

  • New passes: crossing/junction (F; second feature in E, also path · path · junction), bend, free end, which-of-similar (C), side-of / outside corner for compact areas, edge / part / inside & outside corner for extended areas.
  • OCAD Bezier segments are flattened onto the drawn curve; junction snap tolerance 0.3 mm; within 1.5 mm the junction replaces the plain line candidate.
  • Extended areas rank by depth-from-edge + 0.5 mm, so a boulder beside the control beats the open land it stands in.
  • autoDescribe fills the top candidate on create; on move it replaces an untouched auto description (never a hand-edited one). The menu offers a single replacement suggestion only when it differs.

Also fixes a flaky runner-count read in structured-search-names.spec.ts.

Docs

docs/course-editor.md, docs/course-maps.md, docs/control-descriptions-and-editor-geometry.md, docs/features.md, new docs/bugfix-control-description-columns.md.

Verification

  • pnpm build — clean
  • Unit: shared 292 · api 588 · web 867
  • Integration: 333
  • Full E2E: 4/4 shards PASS
  • Docker host-db stack rebuilt (oxygen-api / oxygen-web Built + Started)

Major-version drift (informational)

  • vitest 4.1.10 → 5.0.1, @vitest/coverage-v8 4.1.10 → 5.0.1
  • typescript 6.0.3 → 7.0.2
  • dotenv 17.4.2 → 18.0.3
  • prisma 7.9.1 → 8.0.0-rc.15 (RC — do not bump yet)

Out of scope / follow-ups

Purple Pen / IOF XML import-export of descriptions, upper/lower column C from contours, dimension symbols 9.x beyond free text.

Data model and sheet
- ControlDescription gains e (appearance / second feature) and h
  (other information); dimensions move from column E to F as the spec
  requires. One shared descriptionCells() converter replaces the two
  duplicated OCAD->IOF converters.
- courses.description_instructions (JSONB, new migration) holds
  special-instruction rows (13.x) and the finish variant (14.x).
- Shared buildDescriptionSheet() row model: 3-row IOF header (event /
  classes / course . length . climb), start row, controls, specials,
  finish with the measured last-control -> finish distance. Used by the
  on-map sheet in the course editor, the print block and the layout
  editor preview so screen and print are identical.
- IOF sheet rules: bold header with thick cell rules, thick line under
  start, after every third description, on either side of a special
  instruction and above the finish; thick verticals after columns C
  and F (A B C | D E F | G H); alternate row shading.

Editor
- Reworked ControlDescriptionEditor: labelled 3x3 compass grid for
  column C, visible names under every symbol (mobile), E/H sections,
  live summary sentence, bottom sheet on narrow screens. en/sv.
- Crosshair cursor in editor mode and a magnifier loupe while dragging
  (MapLoupe over passive TileLayers sharing the blob cache).
- Context-menu actions are disabled instead of silently ignored while
  a save is in flight or the control list has not loaded.

Autodetect
- New passes: crossing/junction (F, with the second feature in E, also
  path . path . junction), bend, free end, which-of-similar (C),
  side-of / outside corner for compact areas, edge / part / inside and
  outside corner for extended areas.
- OCAD Bezier segments are flattened onto the drawn curve; junction
  snap tolerance 0.3 mm; within 1.5 mm the junction replaces the plain
  line candidate.
- Extended areas rank by depth-from-edge + 0.5 mm so a boulder beside
  the control beats the open land it stands in.
- autoDescribe fills the top candidate on create; on move it replaces
  an untouched auto description (never a hand-edited one). The menu
  offers a single replacement suggestion only when it differs.

Also fixes a flaky runner-count read in structured-search-names.spec.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ellmkay
ellmkay merged commit 8b2f698 into main Sep 24, 2026
1 check passed
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.

2 participants