Skip to content

feat(review): local browser review app + demo release candidate#24

Open
shaypal5 wants to merge 4 commits into
mainfrom
feat/review-app
Open

feat(review): local browser review app + demo release candidate#24
shaypal5 wants to merge 4 commits into
mainfrom
feat/review-app

Conversation

@shaypal5

Copy link
Copy Markdown
Contributor

Summary

Adds a fully-featured local browser review UI for surveying a letter_set.json release candidate, plus a synthetic demo candidate to review immediately.

New: hletterscriptgen review CLI subcommand

hletterscriptgen review examples/demo_candidate/demo_writer_0001/letter_set.json
# → opens http://localhost:8765/

Review UI features

Feature Detail
Sticky header Writer ID, generation date, live progress bar (N/total reviewed)
Letter sidebar One item per letter; per-variant coloured verdict dots update in real-time
Variant cards Pixelated glyph image (base64-embedded), metadata table, verdict buttons, comment textarea
Verdicts ✅ Accept · ❌ Reject · 🔄 Request Changes (toggle-able)
Persistence Auto-saves to .review_feedback.json next to the letter-set on every Save
Bulk actions "Accept all unreviewed" · "Export" downloads feedback JSON
UX Unsaved-changes guard on page unload; sidebar highlights current section on scroll

No external CDN deps — single-page app, all images inline as base64, pure stdlib HTTP server.

New: scripts/make_demo_candidate.py

Generates examples/demo_candidate/ with 29 synthetic PNG glyph crops across 15 Hebrew letters (Alef–Tav), drawing each with a stdlib-only Bresenham line renderer (zero extra deps). The resulting letter_set.json is schema-valid and ready to review.

Files changed

File Description
src/hletterscriptgen/reviewer.py New — HTML builder + HTTP server
src/hletterscriptgen/cli.py +review subcommand
tests/test_reviewer.py 37 new tests (HTTP layer, HTML builders, utility functions)
scripts/make_demo_candidate.py New — synthetic release candidate generator
examples/demo_candidate/ Generated demo tree (29 PNGs + letter_set.json)
pyproject.toml Suppress RUF001 on reviewer.py (intentional Hebrew dict keys)

Test results

  • 175 tests, all pass
  • Coverage: 91.26% (threshold: 90%)
  • ruff: clean
  • mypy strict: clean

🤖 Generated with Claude Code

shaypal5 and others added 4 commits May 25, 2026 09:42
- Add `hletterscriptgen review <letter_set.json>` CLI subcommand that serves
  a single-page review UI at localhost:8765 (configurable with --port).

- `src/hletterscriptgen/reviewer.py`: builds an HTML page that embeds all
  glyph images as base64 data URIs; no external CDN deps, pure stdlib HTTP
  server.

  UI features:
  - Sticky header with writer ID, generation date, and live progress bar
  - Sticky letter sidebar with per-variant verdict dots (green/red/orange)
  - Per-variant cards: pixelated glyph image, metadata table (size, ink_ratio,
    source entry, bbox, license), verdict buttons (Accept/Reject/Changes),
    comment textarea, Save button
  - `GET /feedback` / `POST /feedback` auto-persist to
    `.review_feedback.json` next to the letter-set file
  - "Export" button downloads feedback JSON; "Accept all unreviewed" bulk action
  - IntersectionObserver-driven sidebar highlight on scroll
  - Unsaved-changes guard on page unload

- `scripts/make_demo_candidate.py`: stdlib-only synthetic glyph generator
  (zero extra deps beyond what the project already uses for tests).  Draws
  15 Hebrew letter shapes (Alef–Tav) using Bresenham line strokes, produces
  29 PNG crops across 2-3 size variants per letter, writes a schema-valid
  `letter_set.v1` document.

  Usage:
      python3 scripts/make_demo_candidate.py
      hletterscriptgen review examples/demo_candidate/demo_writer_0001/letter_set.json

- `tests/test_reviewer.py`: 37 tests covering utility functions, HTML
  builders, HTTP GET/POST handler, and CLI error paths; no flaky threading
  issues (one-shot `handle_request()` per test).

- `pyproject.toml`: suppress RUF001 on reviewer.py (intentional Hebrew
  character dict keys).

Tests: 175 pass, coverage 91.26%, ruff+mypy clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ns to hash (#25)

HeOCR/public-domain-hand-written-hebrew-scans was renamed to HeOCR/hash
(HASH — Hebrew Archive of Scanned Handwriting). Update all references
across source, tests, fixtures, docs, and examples.

Also update segmentation-approach.md with current corpus size:
373 entries, 111 sources, 48 unique creators (was 60 entries at spike time).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix class-level shared state on _ReviewHandler: create a fresh _Handler
  subclass inside serve() per invocation so state is never shared across
  server runs or concurrent tests.
- Fix atomic feedback write: write to a .tmp sibling then replace() so a
  killed process never leaves a corrupt .review_feedback.json.
- Fix HTML/JS injection: replace all inline onclick/oninput handlers with
  event delegation driven by data-vid/data-verdict attributes; HTML-escape
  all user-controlled values with html.escape() in attribute contexts.
- Remove dead feedback_path parameter from _build_html(); it was accepted
  but never referenced inside the function.
- Serve images via /image/<vid> HTTP endpoint instead of base64-embedding
  every asset into the HTML; _build_sections now returns an images_map and
  the handler routes /image/* lookups through it (dict lookup prevents path
  traversal).
- Wrap bare dict key access in _build_variant_card with try/except raising
  ValueError with a descriptive message.
- Drain POST body before sending 404 to avoid TCP RST / ConnectionResetError
  on unknown paths.
- Fix tests: fresh per-test handler subclass in _start_one_shot_server so no
  state leaks between cases; update all callers for new API signatures; add
  tests for /image/ endpoint, atomic write, data-attribute HTML, and malformed
  variant error.
- make_demo_candidate.py: import __version__ instead of hardcoding "0.1.0.dev0";
  remove redundant inline imports (zlib/struct already at module top); remove
  unused letters_dir variable; clarify why _ink_ratio is not imported from
  extractor (stdlib-only constraint).
- cli.py: replace unreachable parser.error() with AssertionError to satisfy
  mypy while making the invariant explicit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

1 participant