feat: badge v2 auto-scaling text with Element_fit_text

Adds binary-search font auto-scaling for badge text fields, replacing
the character-count heuristic in v1. New files:

- action_fit_text.ts: Svelte action using binary search + MutationObserver
  + ResizeObserver. Pass null to disable (manual override mode).
- element_fit_text.svelte: Component wrapper with min/max/manual_size/
  height/width props. height prop required for overflow detection to work.
- ae_comp__badge_obj_view_v2.svelte: Badge render using Element_fit_text
  for name/title/affiliations/location in display mode. font_size_* props
  default to undefined (auto-scale) instead of numeric defaults.
  fit_heights derived object provides layout-aware section heights for
  badge_3.5x5.5_pvc, badge_4x5_fanfold, and badge_4x6_fanfold layouts.
  flex_justify() maps shorthand ('around','between','even') to CSS values.
  Edit mode uses plain divs — inputs are never auto-scaled.

print/+page.svelte: Added v1/v2 toggle button in header. V1 preserved
as fallback. font_size_* passed as null (not ?? undefined) to v2 so
auto-scaling is active by default; manual override from print controls
still disables it per-field.

Docs: PROJECT__AE_Events_Badges_Review_Print.md updated with kiosk
workflow design intent, email address rule (always event_badge.email),
permission model alignment gap (TASK 4.0), and v2 implementation status.
TODO__Agents.md: completed items removed, badge polish tasks updated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-03-12 13:23:47 -04:00
parent dfad2831d6
commit 2198c55e27
6 changed files with 1789 additions and 38 deletions

View File

@@ -17,8 +17,13 @@
import { ArrowLeft, Eye, LoaderCircle, Mail, Printer } from 'lucide-svelte';
import Comp_badge_obj_view from '../ae_comp__badge_obj_view.svelte';
import Comp_badge_obj_view_v2 from '../ae_comp__badge_obj_view_v2.svelte';
import Comp_badge_print_controls from '../ae_comp__badge_print_controls.svelte';
// V2 toggle: temporary — lets staff compare auto-scaling text (v2) vs heuristic sizing (v1).
// Remove once v2 is verified and v1 is retired.
let use_v2 = $state(false);
let event_badge_id = $derived(page.params.badge_id);
let event_id = $derived(page.params.event_id);
@@ -171,6 +176,16 @@
<!-- Right: Action buttons -->
<div class="flex flex-row gap-1 items-center shrink-0">
<!-- V1/V2 toggle: temporary comparison tool — remove once v2 is verified -->
<button
type="button"
class="btn btn-sm preset-tonal-surface flex items-center gap-1 font-mono"
onclick={() => (use_v2 = !use_v2)}
title={use_v2 ? 'Switch to v1 (heuristic sizing)' : 'Switch to v2 (auto-scaling text)'}
>
{use_v2 ? 'v2' : 'v1'}
</button>
<!-- 1. Print Now: Trusted+, not printed OR Edit Mode (reprint) -->
{#if is_trusted && (!is_printed || is_edit_mode)}
<button
@@ -221,17 +236,33 @@
pr-64 offsets the badge area so it's not hidden under the fixed controls panel.
On print, pr-64 is cleared and the fixed controls panel is hidden. -->
<div class="print:pr-0 pr-64">
<Comp_badge_obj_view
event_id={$lq__event_badge_obj.event_id as string}
event_badge_id={event_badge_id as string}
{lq__event_badge_obj}
{lq__event_badge_template_obj}
is_review_mode={false}
font_size_name={font_size_name ?? undefined}
font_size_title={font_size_title ?? undefined}
font_size_affiliations={font_size_affiliations ?? undefined}
font_size_location={font_size_location ?? undefined}
/>
{#if use_v2}
<!-- V2: pass null directly (not ?? undefined) so auto-scaling is active by default.
null → Element_fit_text auto-scales; a number → manual override from print controls. -->
<Comp_badge_obj_view_v2
event_id={$lq__event_badge_obj.event_id as string}
event_badge_id={event_badge_id as string}
{lq__event_badge_obj}
{lq__event_badge_template_obj}
is_review_mode={false}
font_size_name={font_size_name}
font_size_title={font_size_title}
font_size_affiliations={font_size_affiliations}
font_size_location={font_size_location}
/>
{:else}
<Comp_badge_obj_view
event_id={$lq__event_badge_obj.event_id as string}
event_badge_id={event_badge_id as string}
{lq__event_badge_obj}
{lq__event_badge_template_obj}
is_review_mode={false}
font_size_name={font_size_name ?? undefined}
font_size_title={font_size_title ?? undefined}
font_size_affiliations={font_size_affiliations ?? undefined}
font_size_location={font_size_location ?? undefined}
/>
{/if}
</div>
<!-- Controls panel: fixed to the right edge of the viewport — screen-only.