Skip to content

develop#94

Open
ariedotcodotnz wants to merge 22 commits into
mainfrom
develop
Open

develop#94
ariedotcodotnz wants to merge 22 commits into
mainfrom
develop

Conversation

@ariedotcodotnz

Copy link
Copy Markdown
Owner

No description provided.

ariedotcodotnz and others added 22 commits January 14, 2026 10:26
…side using `logger.exception`; return user-friendly error messages.
…dd `check` command for verifying Flask-Migrate installation and setup
… and adjust scaling logic for consistency with generated text layout
…e target height, improve consistency with generated text, and add debug logs for preprocessing and overrides
…simplify stroke generation, and dynamically render overrides in SVG gaps.
…es, ensure clean SVG insertion, and improve debug logs for exclusion zones and rendering steps.
…s for precise override positioning, implement model-level cutting, and refine rendering logic for transitions.
…reserve RNN context, refine chunking, and improve line stitching with attention-based character indices.
…ezier curve support, simplify exclusion logic, and enhance debug logs.
… override position tracking, and refine stroke generation logic.
…efine character index handling, and enhance debug logs for stroke generation with styles.
…ogic, implement auto-detection from data, and refine override position calculations with enhanced debug logs.
…n-based shifting for gap creation, and simplify rendering logic by removing exclusion zones.
…ld-based character searches, refine override positioning, and improve debug logging for gap and shift calculations.
… long words

Improvements to text chunking (operations/chunking.py) -- the step that splits
text before generation and drives how lines fill. No model retraining.

- Fix 'balanced' strategy: its punctuation branch sat behind an if/elif on
  overlapping sets and was unreachable, so the DEFAULT strategy never broke at
  commas/semicolons. 'balanced' now considers sentence + punctuation breaks.
- Honor target_chars_per_chunk: it was accepted everywhere but never used. The
  character budget now bounds the break-search window so breaks land on real
  punctuation that also fits the target, giving even line filling.
- Hard-split pathological space-less tokens (URLs, long ids) so they cannot
  exceed the model's sequence limit or run off the page (lossless reassembly).
- Guarantee loop progress (>=1 word/iter) under degenerate params.

Adds model-free tests (tests/test_operations.py) covering all strategies, the
character budget, long-word splitting, and whitespace handling. 8/8 passing.

Note: the original local work also touched _draw.py/Hand.py (override spacing,
per-line auto-size). Those were dropped during rebase because origin/develop
independently rewrote that override/layout subsystem (char_indices,
effective_target_h) and supersedes them; this commit keeps only the
upstream-untouched chunking improvements.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 10, 2026 04:28
flash('Invalid log file path.', 'error')
return redirect(url_for('admin.error_logs'))

if os.path.exists(filepath):

if os.path.exists(filepath):
try:
with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
flash('Invalid log file path.', 'error')
return redirect(url_for('admin.error_logs'))

if not os.path.exists(filepath):

log_activity('admin_action', f'Downloaded log file: {filename}')

return send_file(filepath, as_attachment=True, download_name=filename)
flash('Invalid log file path.', 'error')
return redirect(url_for('admin.error_logs'))

if not os.path.exists(filepath):

try:
# Truncate the file
with open(filepath, 'w') as f:
if not file_path.startswith(base_path + os.sep) and file_path != base_path:
# Check if the file exists before attempting to serve
file_path = os.path.join(base_directory, safe_filename)
if not os.path.isfile(file_path):

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes broad changes across the Flask webapp and the handwriting engine to improve “natural” sizing/layout (writing-size in mm, page-fill auto sizing, balanced chunk line breaking), add optional text reflow, expand character-override rendering using attention-derived character indices, and introduce an admin error-log viewer while tightening error responses/logging.

Changes:

  • Add “natural” handwriting sizing controls (writing_size_mm) and optional input reflow (reflow) through UI → API → generation pipeline.
  • Rework chunking/wrapping behavior (balanced DP line breaks, char-budgeted chunking, hard-splitting pathological tokens) and add model-free tests for operations + sizing.
  • Add admin log viewing/downloading/clearing UI and routes; reduce user-facing exception detail across several endpoints.

Reviewed changes

Copilot reviewed 27 out of 28 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
webapp/utils/text_utils.py Adds reflow_paragraphs() to join soft-wrapped paragraphs before wrapping/generation.
webapp/utils/generation_utils.py Plumbs writing_size_mm + reflow through param parsing and generation dispatch.
webapp/templates/index.html Updates sizing UI copy and adds “Writing Size (mm)” + “Reflow text” controls.
webapp/templates/admin/logs.html New admin UI for browsing/filtering/downloading/clearing log files.
webapp/templates/admin/character_overrides/view.html Tweaks override preview stroke-width input defaults/labeling.
webapp/templates/admin/base.html Adds “Error Logs” to admin navigation.
webapp/static/js/modules/alpine-app.js Adds frontend state + request payload fields for writing_size_mm and reflow.
webapp/routes/style_routes.py Switches to send_from_directory and logs exceptions server-side with generic client error.
webapp/routes/presets_routes.py Replaces detailed exception strings with server-side logging + generic error responses.
webapp/routes/job_routes.py Removes internal exception details from API responses; logs stack traces server-side.
webapp/routes/generation_routes.py Splits ValueError vs generic exceptions and logs exceptions; changes error payloads.
webapp/routes/character_override_routes.py Replaces print() exception logging with current_app.logger.exception() and generic error text.
webapp/routes/batch_routes.py Replaces print() debugging/errors with structured logging + generic client error.
webapp/routes/admin_routes.py Adds admin log management endpoints and supporting parsing/sanitization helpers.
webapp/init_db.py Adds schema reconciliation for missing columns + stamps Alembic head; seeds default page sizes.
tests/test_sizing.py Adds model-free dimensional tests for the new natural sizing behavior in _draw.
tests/test_operations.py Adds model-free tests for the updated chunking + balanced line-break behavior.
handwriting_synthesis/rnn/RNN.py Extends sampling to return attention-derived char_indices per timestep.
handwriting_synthesis/hand/operations/sampling.py Adds optional return of attention char_indices alongside strokes.
handwriting_synthesis/hand/operations/chunking.py Adds balanced DP line breaking + char-budgeted chunking + hard split of long tokens.
handwriting_synthesis/hand/operations/init.py Exports balanced_line_breaks.
handwriting_synthesis/hand/Hand.py Plumbs writing_size_mm; overhaul override handling to use space placeholders + attention indices.
handwriting_synthesis/hand/character_override_utils.py Improves SVG path coordinate extraction to include curve commands for bounding boxes.
handwriting_synthesis/hand/_draw.py Major sizing/layout rework (natural x-height, width clamp, vertical fit) + override insertion logic.
deploy/db-migrate.sh Ensures FLASK_APP=webapp.app:app is set for Flask-Migrate commands; adds a check command.
CLAUDE.md Adds repository guidance (commands, pipeline overview, model constraints, gotchas).
.claude/settings.local.json Expands allowed bash commands for Claude tooling (wc/grep).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +842 to +845
# Security check: ensure the file is within logs_dir
real_logs_dir = os.path.realpath(logs_dir)
real_filepath = os.path.realpath(filepath)
if not real_filepath.startswith(real_logs_dir):
Comment on lines +916 to +920
# Security check: ensure the file is within logs_dir
real_logs_dir = os.path.realpath(logs_dir)
real_filepath = os.path.realpath(filepath)
if not real_filepath.startswith(real_logs_dir):
flash('Invalid log file path.', 'error')
Comment on lines +945 to +949
# Security check: ensure the file is within logs_dir
real_logs_dir = os.path.realpath(logs_dir)
real_filepath = os.path.realpath(filepath)
if not real_filepath.startswith(real_logs_dir):
flash('Invalid log file path.', 'error')
Comment on lines +869 to +880
# Apply filters
if level_filter != 'all' and entry['level'] != level_filter:
continue

if search_query and search_query.lower() not in entry['clean'].lower():
continue

log_entries.append(entry)

# Limit entries
if len(log_entries) >= lines_limit:
break
Comment thread webapp/init_db.py
Comment on lines +84 to +90
conn.execute(text(
f'ALTER TABLE "{table.name}" ADD COLUMN "{column.name}" {col_type}'))
if not column.nullable:
conn.execute(
text(f'UPDATE "{table.name}" SET "{column.name}" = :val '
f'WHERE "{column.name}" IS NULL'),
{"val": _placeholder_for(column)})
Comment on lines +226 to +239
# Convert to line_segments format (single segment per line, like non-override)
line_segments = []
for line_idx, (original_line, strokes, char_indices) in enumerate(
zip(lines, generated_strokes, char_indices_list)
):
line_segments.append([{
'type': 'generated',
'text': original_line, # Keep original text for reference
'modified_text': modified_lines[line_idx], # Text that was actually generated
'strokes': strokes,
'char_indices': char_indices, # NEW: Character index per stroke from attention
'line_idx': line_idx,
'override_positions': override_positions[line_idx] # [(char_idx, char), ...]
}])
Comment on lines +212 to +214
print(f"DEBUG: Original lines: {lines}")
print(f"DEBUG: Modified lines (with placeholders): {modified_lines}")
print(f"DEBUG: Override positions: {override_positions}")
Comment on lines +86 to +90
A run of consecutive non-blank lines is treated as a single paragraph and
joined with spaces (so the generator re-wraps it to the page width); blank
lines are preserved as paragraph separators. This prevents short, hard-wrapped
input (e.g. a letter pasted with a line break every few words) from rendering
as a column of half-empty lines. Turn it off to keep your exact line breaks.
Comment on lines +114 to +116
except Exception as e:
current_app.logger.exception('Generation error')
return jsonify({"error": "Failed to generate handwriting. Please check your parameters."}), 400
Comment on lines +462 to +467
<label class="bx--checkbox-wrapper m-0">
<input id="reflowText" type="checkbox" class="bx--checkbox" x-model="reflowText" />
<label for="reflowText" class="bx--checkbox-label">
<span class="bx--checkbox-label-text">Reflow text to fill width (Recommended)</span>
</label>
</label>
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.

3 participants