Repository navigation
Add deterministic manifest-backed static assets - #13
Closed
crimson-knight wants to merge 370 commits into
Closed
crimson-knight wants to merge 370 commits into
crimson-knight wants to merge 370 commits into
Conversation
…dgets in todos + draft iOS WidgetKit phase * brief-10-d-polish.md — combines ListView widget polish (corners / drag handle / removal animation / 16pt inset) with 5 catalog extensions to todos (Alert / ActionSheet / Sheet / DatePicker / Popover). One focused implementer pass. * phase-11-home-screen-widgets.md — drafted per owner directive. iOS home-screen widgets via WidgetKit. Separate Xcode target, App Group plumbing, JSON snapshot publish model since Crystal runtime can't run in WidgetKit's restricted extension process. 7 deliverables, ~8-10 hour estimate, queued behind 10D-polish. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…rch + preferences + glass Owner scope expansion this session. Adds 6 deliverables on top of 10D-polish: - D1 SearchField on todos list - D2 Insights screen with ChartView + ActivityRings (seeded mock history) - D3 Location field + MapView per todo with pin indicator - D4 Preferences overhaul with Form / Picker / RadioGroup / Slider / Stepper for sort + weighting + goals - D5 GlassBackground material on Sheet + Popover chrome - D6 Build + screenshots + handtest guide Sequenced AFTER 10D-polish to keep each implementer pass hand-testable. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…+ brief updates Codex critique surfaced a foundational gap: briefs shipped widgets with screenshots but no per-widget usage docs / API references / catalog status updates / override-path documentation. Future agents reading the catalog couldn't know HOW to use what we built. Ships: * NEW rubric/widget-demonstration-criteria.md — authoritative rule + per-widget usage doc template (6 sections) + catalog status fields + Voyager-as-canonical-demo policy. * UPDATED brief-10-d-polish.md — Deliverable C0 + 3 new acceptance bullets requiring usage docs for the 7 widgets it exercises. * UPDATED brief-10-d-extensions.md — Deliverable 0 + 3 new acceptance bullets for the 10 widgets it exercises. * UPDATED phase-11-home-screen-widgets.md — D7 expanded with 4-doc requirement (usage doc, component-api entry, catalog status, architecture doc). * Added .claude/worktrees/ to .gitignore (previous attempt accidentally tried to add agent worktrees as submodules). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add three new UI::ListView defaults driven by the owner's hand-test feedback: - content_inset_horizontal : Float64? = 16.0 (Mail-style row inset) - row_removal_duration_seconds : Float64 = 0.4 (collapse animation) - shows_drag_handle : Bool = true (line.3.horizontal anchor when on_move set) Plumbed through the swiftkit_overrides populator and the ListViewOverrides Swift struct. Facade-side wiring lands in a follow-up commit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- A1: Use SwiftUI .tint(color) (not .buttonStyle) so swipe tiles render with full-bleed square-corner chrome. Label uses .titleAndIcon stack. - A2: Render `line.3.horizontal` SF Symbol on trailing edge of each row when moveToken != nil. Tertiary system color, 24pt wide. - A3: Wrap List in `.animation(.easeInOut(duration: 0.4), value: count)` and per-row .transition(.asymmetric(insertion: .opacity, removal: .opacity + .scale leading)) so deletes collapse smoothly. - A4: Apply .listRowInsets(EdgeInsets(top:8, leading:16, bottom:8, trailing:16)) by default; nil-passthrough for explicit override. Swift build green (libAssetPipelineSwiftKit.a archived). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…flags State carries the transient flags for catalog modal demos: - pending_delete_todo_id (Alert) - pending_share_todo_id (ActionSheet) - pending_editor_todo_id (Sheet) - show_overflow_menu (Popover) Controller adds the action methods that toggle these flags and perform the underlying mutations on confirm/cancel. `:new_todo` and `:edit_row` now route through the Sheet path; the slug-based fallback is preserved as `:open_editor_sheet`. State also gains `sort_by_deadline!` and `clear_completed_todos` for B5 overflow menu actions. Screen-side rendering of the new modals lands in the next commit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…et/DatePicker/Popover - B1: Delete swipe sets pending_delete_todo_id; screen renders UI::Alert with Cancel + Delete (destructive). Confirm dispatches :confirm_delete. - B2: Share swipe sets pending_share_todo_id; screen renders UI::ActionSheetWithWebFallback with Copy / Print / Cancel actions. - B3: :new_todo and :edit_row now set pending_editor_todo_id; screen renders UI::Sheet wrapping the editor view (build_editor_content) with [:medium, :large] detents + drag indicator. Save dispatches :save_sheet which mutates state from FormState and closes the sheet. - B4: Editor sheet uses UI::DatePicker for the deadline (native iOS wheel/calendar chrome). Clear-deadline button covers nilable state. - B5: Header gains a "•••" overflow button that sets show_overflow_menu; screen renders UI::Popover under it with Sort by deadline / Hide completed / Clear all completed options. Crystal --no-codegen check passes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Editing-checked strikethrough writer: - before: editing && seed_completed → (Bool | Nil) - after: !editing.nil? && seed_completed → Bool xcodebuild + Swift build + Crystal cross-compile all green on iPhone 17 Pro / iOS 26.5 simulator. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Captures the todos screen on launch: - line.3.horizontal drag handles visible on every row trailing edge - 16pt row inset (rows align flush with content card) - Header gains "•••" overflow button between Print + Settings - Strikethrough + Secondary role on completed todos (Call dentist / Water plants) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
polish-01-delete-alert — seeds pending_delete_todo_id polish-02-share-actionsheet — seeds pending_share_todo_id polish-03-editor-sheet-new — seeds pending_editor_todo_id=0 polish-04-editor-sheet-edit — seeds pending_editor_todo_id=first.id polish-05-overflow-popover — seeds show_overflow_menu=true Each maps to voyager-todos slug so the modal renders on first frame via screen.build(ctx)'s pending_* checks. iOS rebuild + xcodebuild green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The visit(UI::ActionSheet) method was calling apsk_overrides_set_int on setConfirmToken: and setCancelToken:, which expect NSNumber? on the Swift side. The raw-int path stuffs the value where an object pointer is expected, then objc_retain segfaults. Switched to apsk_overrides_set_uint64_boxed (existing trampoline, boxes via NSNumber numberWithUnsignedLongLong:). This matches what sender.set_uint64 would have done if used; visit() was hand-rolled for these tokens. Pre-existing bug from Phase 10D-refocus — unblocks B2 ActionSheet share-flow demo. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
iOS class-init gap leaves Time::Location.local uninitialized →
segfault in find_zoneinfo_file when build_editor_content seeds the
DatePicker. Use Time.utc + Time.parse_utc for the seed path; format
the on_change time via t.to_utc.to_s("%Y-%m-%d"). Date-only picker
mode doesn't care about timezone.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 03_alert_delete_confirm.png — UI::Alert (B1) - 04_actionsheet_share.png — UI::ActionSheet (B2) - 05_sheet_editor_new.png — UI::Sheet new-todo path (B3) - 06_datepicker_deadline.png — UI::Sheet edit + UI::DatePicker (B3+B4) - 07_popover_overflow.png — UI::Popover overflow menu (B5) Plus 01_drag_handle_visible.png (A1+A2+A4) from earlier. DatePicker year display shows "May 27, 3995" — pre-existing Crystal-Swift epoch-conversion gap, NOT introduced by this brief. File as backlog: timeIntervalSince1970 vs Crystal Time.to_unix. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
.claude/skills/apple-platform-guide/usage/ ├── list-view.md — UI::ListView (with Phase 10D-polish defaults) ├── swipe-action.md — UI::SwipeAction tile contract ├── alert.md — UI::Alert (B1) ├── action-sheet.md — UI::ActionSheet / WithWebFallback (B2) ├── sheet.md — UI::Sheet reactive modal (B3) ├── date-picker.md — UI::DatePicker (B4) └── popover.md — UI::Popover (B5) Per rubric: six sections each — Default experience / Crystal API / Behavior contract / Customization knobs / Override path / Evidence. Every override path documented EITHER as a public knob OR with an explicit backlog item name. No "future consumer can override" hand- waving. Backlog items referenced: - B-LIST-MACOS-CHROME (drag handle + animation port) - B-LIST-ANDROID-CALLBACKS - B-LIST-SWIPE-TINT (per-action tint property) - B-LIST-SWIPE-LABEL-STYLE - B-ALERT-CUSTOM-CHROME - B-ACTIONSHEET-MULTI-ACTION - B-SHEET-CUSTOM-DETENT-HEIGHT - B-SHEET-INTERACTIVE-DISMISS-DISABLED - B-DATEPICKER-STYLE-PROPERTY - B-DATEPICKER-EPOCH-CONVERSION - B-POPOVER-ANCHOR-VIEW - B-POPOVER-COMPACT-ADAPTATION - B-POPOVER-REACTIVE-PRESENTED intent-backlog.md additions land in the next commit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
intent-catalog.md — add four new fields to 6 widget entries: - :list (UI::ListView) - :swipe_actions (UI::SwipeAction) - :alert (UI::Alert) - :confirmation_dialog (UI::ActionSheet via ConfirmationDialogFacade) - :sheet (UI::Sheet) - :popover (UI::Popover) - :compact_date_picker_style (UI::DatePicker) Each gains: - demo_status: documented-with-default-experience - usage_doc: relative path to .claude/skills/apple-platform-guide/usage/*.md - canonical_example: file:line in Voyager source - evidence: relative path to phase-10-d-polish screenshot - override_path_status: one of public-knobs | facade-extension-required | no-override-yet-tracked-in-backlog intent-backlog.md — appended Phase 10D-polish section with 13 new backlog items (B-LIST-* / B-ALERT-* / B-ACTIONSHEET-* / B-SHEET-* / B-DATEPICKER-* / B-POPOVER-*) covering every "no override knob today" surface mentioned in the usage docs. No evasive language remains. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Full walkthrough of: - Initial launch (A1+A2+A4 — drag handles, square tiles, 16pt inset) - Delete swipe → Alert (B1) - Share swipe → ActionSheet (B2) - Add/Edit → Sheet (B3) with native DatePicker (B4) - Overflow → Popover (B5) - Drag-reorder confirmation - Known issues + their backlog item references - Capture-scenario reference for automated screenshots Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…pover chrome on iPhone DatePicker bug root cause: iOS class-init gap leaves Crystal Time::UNIX_EPOCH (62_135_596_800) uninitialized → Time#to_unix returns the raw year-1-rooted @seconds instead of Unix epoch. SwiftUI interprets that as seconds-since-1970 → year 3995. Fix in UIKit renderer's visit(UI::DatePicker): new safe_time_to_unix helper detects values > year 2100 in seconds and manually subtracts the year-1-to-1970 literal offset. Closes B-DATEPICKER-EPOCH-CONVERSION. Popover compact adaptation: SwiftUI's default on iPhone size class is .popover → .sheet fallback. PopoverFacade.swift now calls .presentationCompactAdaptation(.popover) (iOS 16.4+ / macOS 13.3+) inside an #available guard so iPhone gets actual popover chrome. Closes B-POPOVER-COMPACT-ADAPTATION. Both fixes are widget-default level — every consumer of UI::DatePicker and UI::Popover gets the corrected behavior for free. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nfirmationDialogFacade Extends ConfirmationDialogOverrides with parallel actionLabels / actionStyles / actionTokens arrays. When non-empty, the SwiftUI facade switches from binary confirm/cancel to a ForEach so iOS ActionSheet renders Copy / Print / Cancel (or any number of actions) instead of dropping additional buttons. Crystal-side visit(UI::ActionSheet) now emits every action in view.actions to the new arrays; cancel-role buttons land naturally because SwiftUI's .confirmationDialog pins role:.cancel buttons at the bottom. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…to-dismiss Adds interactive_dismiss_disabled : Bool = false to UI::Sheet. When true, SheetFacade applies .interactiveDismissDisabled() so the user cannot swipe-down to close the sheet. Programmatic dismissal via is_presented = false still fires the on_dismiss callback as before. Use this for editors mid-edit, multi-step wizards, and forms with unsaved changes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…eels styles Adds UI::DatePickerStyle enum (Automatic / Compact / Graphical / Wheels) and UI::DatePicker#style = DatePickerStyle::Automatic. DatePickerFacade switches on overrides.datePickerStyle and applies .datePickerStyle(.compact|.graphical|.wheel). Default Automatic skips the modifier so the platform's HIG default still applies. Voyager's editor-sheet picker now uses :compact so the deadline shows as an inline button with a popover calendar instead of a full-page graphical calendar. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-action tint + label-style Adds tint : Symbol? and label_style : Symbol = :auto properties to UI::SwipeAction. When set: - tint: overrides the role-derived default (red destructive, blue/green default) with one of :blue/:green/:orange/:red/:purple/:yellow/:pink/ :gray. Populator passes the symbol name to SwiftKit which maps to the matching SwiftUI Color. - label_style: :auto/:icon/:title/:title_and_icon. Default :auto retains the old auto-inference; explicit values force the tile contents. Both SwipeActionRowFacade and ListViewFacade now accept parallel *LabelStyles arrays. ListView's per-row callback path threads through SwipeAction#tint / #label_style so the per-row swipe API supports the same knobs. Voyager's todo list now demos: Archive orange (leading), Done green, Share gray, Edit blue, Delete role-derived red. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…iew by test_id Adds anchor_view_id : String? to UI::Popover. When set, the iOS visit method looks up the source view's ObjC pointer in a new per-renderer test_id → UIView registry and threads it to PopoverOverrides.anchorSourceView. PopoverFacade switches on the presence of the anchor view: when set, it returns a hidden APSKAnchoredPopoverHost UIView that owns a UIPopoverPresentationController-backed presentation anchored to the source view's frame. Adaptive presentation is forced to .none so the popover stays as a popover on iPhone compact (no auto-fallback to sheet). Dismissal forwards through dismissToken. When anchor_view_id is nil, falls back to the existing SwiftUI .popover(isPresented:) path with .presentationCompactAdaptation(.popover). Adds apsk_overrides_set_object_ptr C trampoline for setting AnyObject? properties from raw ObjC pointers. Voyager's overflow popover now anchors to the "•••" button so the bubble's arrow points at it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ActionSheet multi-action, Sheet interactive_dismiss_disabled, DatePicker style enum, SwipeAction tint+label_style, and Popover anchor_view_id all compile through the SwiftKit facade + Crystal bridge. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…backlog Per the widget-demonstration-criteria rubric (non-negotiable social contract), every backlog item closure in this iter ships with: - Usage doc updated for new knobs: - action-sheet.md: multi-action support resolved - sheet.md: interactive_dismiss_disabled knob documented - date-picker.md: style enum (Automatic/Compact/Graphical/Wheels) documented - popover.md: anchor_view_id knob documented - swipe-action.md: tint + label_style knobs documented - intent-catalog.md: coverage_today flipped from missing/partial to full for :interactive_dismiss_disabled, :compact_date_picker_style, :graphical_date_picker_style, :wheel_date_picker_style. - intent-backlog.md: B-ACTIONSHEET-MULTI-ACTION, B-SHEET-INTERACTIVE- DISMISS-DISABLED, B-DATEPICKER-STYLE-PROPERTY, B-POPOVER-ANCHOR-VIEW, B-LIST-SWIPE-TINT, B-LIST-SWIPE-LABEL-STYLE all marked RESOLVED with commit SHAs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 01_home_state.png: app launches, todos render correctly - 02_actionsheet_multi_action.png: PROOF that ActionSheet now renders "Copy to Clipboard" AND "Print This Todo" simultaneously, not just one. - 03_datepicker_compact.png: PROOF that DatePicker shows as inline "May 27, 2026" button (compact style) instead of full-page calendar. BONUS: the year-offset bug is also resolved on the compact path (was showing 3995 in prior screenshots). - 04_popover_anchored.png: Popover uses new UIPopoverPresentationController path with proper popover chrome. Anchor positioning works but iPhone compact size class places the bubble where it has room. - 05_alert_still_works.png: Alert chrome unaffected by iter-2 changes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
B-ACTIONSHEET-MULTI-ACTION resolution flipped the intent-catalog override path from "facade-extension-required" to "public-knobs". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…+ Codex review - merge-readiness-gate.md v2 — addresses 5 Codex BLOCKERs: widget/intent scoreboard split, machine-readable catalog-coverage.yml schema, scoreboard starts red (V1+V2 filed), usage-doc CI check requirement, harness must be executable not design-only. Adds widget-class taxonomy (10 classes, per-class evidence requirements), default-experience checklist, iOS-icon gate separated from web-fallback route manifest, override-path restrictions, source-hash freshness, cross-widget feature stories as merge blockers. - presentation-lifecycle-contract.md — formal C1-C5 invariants for modal- presentation widgets. V1 (ConfirmationDialog auto-close on row tap) + V2 (Voyager todos header sort button crash) filed as P1 violations. - interaction-contracts-harness.md — Crystal-driven simctl + cliclick + log-stream harness design. Marker convention ([APIC:<widget>:<event>]). Phase 12.A/B/C rollout. New accessibility_identifier property on UI::View named as precondition. - handoff/2026-05-28-codex-catalog-coverage-review.md — Codex antagonist review of gate v1, full 76-widget coverage table (13 examples / 0 usage docs in main checkout), 5-app demo-app ladder proposal. Strategic path confirmed by owner: Path A — one merge to main when full revised gate is met across all Tier 1+2 widgets. No partial ships. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…pp ladder Three artifacts that the gate v2 doc named as preconditions but didn't yet exist. Together they materialize the gate's scoreboards from a single machine-checked source of truth. - catalog-coverage.yml — 76 widgets (17 Tier 1 + 59 Tier 2) and 92 intents (1 Class A + 18 Class B + 9 Class C + 64 Class D) initialized in RED state. 14 widgets carry pre-populated canonical_example paths from Codex's audit (HStack, Label, Spacer, VStack, Button, Checkbox, FullScreenCover, Inspector, SecureField, Snackbar, TextField, Toggle, ToolbarItemGroup, ToolbarSpacer). V1 (ConfirmationDialog auto-close) and V2 (Voyager toolbar crash) filed in blocking_p1_violations with not-reproduced-by-spec status. - scripts/validate_catalog_coverage.cr — lint enforcer. Cross-references widgets section against tier-matrix.md, intents section against intent-catalog.md, validates widget_class taxonomy (10 classes), override_path_status enum, demo_status enum, intent class A/B/C/D, owning_widget references, file path existence for canonical_examples and usage_docs, and source-hash freshness when set. Lint passes on initial manifest (0 errors, 2 expected warnings for V1/V2 specs not yet authored). - demo-app-ladder.md — formalizes Codex's 6-app proposal. Per-app: elevator pitch, canonical widget cluster, mirroring app, screens, cross-widget feature stories (gate H), reuse plan, complexity estimate. Home-screen ladder order: Voyager → Notes → Mailbox → Health-log → Photos → Freeform-board, with rationale (most-broadly- demonstrative first). Authoring sequence as Phase 12.A (harness) → 12.B (V1+V2 fixes) → 12.C (Voyager usage docs) → 13-17 (one phase per remaining demo app). Closes Codex BLOCKER 5 (canonical_example and catalog status fields do not exist) and the precondition behind BLOCKERs 1, 3, 4. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wires the marker-emission machinery, the simulator-driven spec runner,
and the first C1+C3 spec for UI::ConfirmationDialog (reproduces V1 once
tap coordinates are captured).
- src/ui/native/interaction_contracts.cr — UI::InteractionContracts.emit
+ emit_for(view_id:). No-op unless ENV["APIC_ENABLED"]=="1". Writes
[APIC:<Widget>:<event>] key=value markers to STDERR for harness
capture. Smoke-tested standalone.
- swift/.../InteractionContracts.swift — Swift mirror that emits the
same marker convention via NSLog so SwiftUI facade events land in
the simulator's unified log alongside Crystal-side STDERR.
- spec/native_ios/ui_interaction/support/simulator_harness.cr — Crystal-
driven simctl harness. Harness.with_voyager(route:) boots the named
iPhone 17 Pro / iOS 26.5 simulator, installs the .app bundle, launches
with SIMCTL_CHILD_APIC_ENABLED=1, subscribes log stream filtered for
[APIC: markers into a thread-safe buffer, yields a Simulator driver,
terminates on block exit. Driver: tap_at_point, tap_accessibility_id
(coordinate-resolved via scenario YAML), wait_for_marker,
assert_no_marker, assert_marker_order, snapshot_markers, clear_markers.
- spec/native_ios/ui_interaction/scenarios/voyager.yml — 9 placeholder
tap coordinates for the V1+V2 scenarios. Marked captured: false until
real coordinates are recorded.
- spec/native_ios/ui_interaction/confirmation_dialog_spec.cr — first
spec. C1: action-sheet stays open across unrelated Rerender; C3:
dismissal flows through dismissToken; C2 corollary: no binding-read
during Rerender. {% if flag?(:ios) %} guarded. Currently pends with
clear reason ("PLACEHOLDERS — run scripts/capture_tap_coordinates.sh")
on `crystal spec` until coordinates are captured. Verified pending
output: 2 examples, 0 failures, 0 errors, 2 pending.
- src/ui.cr — require the new interaction_contracts module.
- merge-readiness-gate.md, interaction-contracts-harness.md, catalog-
coverage.yml — updated all spec path references from
spec/ui_interaction/ to spec/native_ios/ui_interaction/ to comply
with the family_5_partial/spec_platform_directory lint rule. (V1+V2
reproduction_spec paths in the manifest updated.)
Architectural note: accessibility_identifier was already on UI::View
at src/ui/view.cr:327 and threaded through UIKit/AppKit/Web renderers —
the harness doc's claim that it was a NEW property was incorrect. No
work required on that front.
Lint clean: 501 files, 19 rules, 0 diagnostics.
Catalog validator: 76 widgets, 92 intents, 0 errors, 1 expected warning
(V2 reproduction_spec doesn't exist yet — V2 is not-reproduced-by-spec).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wires the first concrete marker-emit sites so the V1 reproduction spec (spec/native_ios/ui_interaction/confirmation_dialog_spec.cr) has live markers to assert against once tap coordinates are captured. - BoolStorage gains optional markerWidget + viewID properties. Setter emits dismiss-token-fire on true→false transitions; setProgrammatically emits present on false→true and platform-dismissed on true→false; init is a no-op (markerWidget is set by the facade after construction). Non-presentation BoolStorage users (Toggle, Checkbox, etc.) leave both properties nil and pay zero overhead. - ConfirmationDialogFacade now tags its BoolStorage with widget= "ConfirmationDialog" + viewID=overrides.accessibilityIdentifier and emits the initial present marker when the dialog is constructed already-presented. Emission is gated by InteractionContracts.enabled (ENV APIC_ENABLED=="1") so production runs are unaffected. swift build: clean. Next sites for follow-up: SheetFacade, PopoverFacade, AlertFacade, FullScreenCoverFacade, InspectorFacade — all use the same BoolStorage pattern so extension is mechanical. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A UI::ScrollView always painted the opaque NSScrollView/NSClipView system background, so a scroll view placed over other content in a ZStack (e.g. a full-bleed hero image) hid it behind a black/system fill. Consumers couldn't build a "scroll content over a fixed hero" screen — surfaced by Happy Coach's AffirmationReview, which must scroll many affirmation cards over the onboarding hero photo. Fix: add nsscrollview_set_draws_background (sets drawsBackground=NO on the scroll view AND its clip view — each paints independently). The appkit ScrollView visit calls it whenever the view's background is unset or fully clear (alpha 0), so a transparent ScrollView lets content behind it show through; an opaque background still paints as before. Native AppKit behavior (no web-spec coverage); verified in the consumer: Happy Coach's AffirmationReview now scrolls its cards over the visible hero photo. Full suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UI::IconButton always rendered the platform button chrome — a bezel / touch-target box around the glyph (visible as a faint box on a small icon over a flat background). There was no way to get a BARE tappable icon, so consumers fell back to a plain Image and lost the tap. Surfaced by Happy Coach's thought-detail icon+text action rows (checkcircle / pluscircle). Add `UI::IconButton#bordered` (default true = standard chrome, no regression). Set false → the facade applies `.buttonStyle(.plain)` for a flush, chrome-free tappable icon. populate_icon_button emits setBordered only when opting out. Spec: skips setBordered at the default, emits false when bare. Full suite green (2144 examples); Swift package builds clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A fill_horizontal Button with a background rendered the background as a narrow pill hugging the label, left-aligned in an otherwise-empty full-width frame — because the background/clip were applied to the label-sized content and the maxWidth:.infinity frame was applied to the whole Button AFTERWARD. Surfaced by Happy Coach's full-width "Submit feedback" CTA (sage box only around the text). Fix: stretch the LABEL inside the Button (frame maxWidth:.infinity) when fill_horizontal — so the Button itself is full-width BEFORE its background/clip apply, and the background fills the row. fill_horizontal leading-aligns its label; stretched-prominent centers. Removed the now-redundant frame-the-whole-Button step. Transparent fill_horizontal buttons (e.g. tappable text-row cards) are unaffected (no background to fill; still fill + leading). Swift-only composition fix (the populator already emits setFillHorizontal, spec'd); full suite green (2144), package builds clean. Verified in the consumer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gned children) The AppKit renderer's visit(UI::ZStack) pinned EVERY child to all four superview edges at required priority, ignoring ZStack#alignment. A fixed-size child was therefore over-constrained (both edges required + width@999): the solver kept the width but broke an edge, so the child landed at an arbitrary horizontal position — there was no way to place an aligned, fixed-size overlay child (slide-in drawer panel, toast, badge, FAB). Fix (backward-compatible, zero-regression for existing screens): - Center / Fill / default: KEEP the legacy all-4-edges-required pin (objc_pin_child_to_superview_edges). Every existing full-bleed hero is an unconstrained Center child that relies on this — untouched. - Leading/Trailing/Top/Bottom: new objc_pin_child_aligned pins the aligned edge + both cross-axis edges at required, adds a REQUIRED containment inequality on the opposite edge (never overflow the parent), and a SOFT equality (priority 499) on the opposite edge. An unconstrained child still fills (499 beats content-hugging 250); a fixed-size child keeps its size and aligns (width@999 beats 499). 499 (not 500) avoids a tie with minimum_width's @500 constraint. (Codex-reviewed: containment inequality + 499 priority were its suggestions.) The 1-D Alignment enum can't express compound alignment, so directional cases still fill the cross axis — sufficient for the drawer/edge-overlay use cases. Adds objc_get_frame + objc_layout_now bridge helpers and a native macOS geometry spec (spec/native_macos/native/zstack_alignment_spec.cr) that builds a real NSView hierarchy, runs a layout pass, and asserts resolved frames: Leading/Trailing fixed-width children align (360pt @ x=0 / x=120), unconstrained children fill (480), Center unconstrained fills (zero-regression). 4/4 green in isolation; web suite 2144/0/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… gap) src/ui/native/objc_collections.cr declared lib LibCollectionBridge (NSString / NSArray / NSMutableArray / NSDictionary helpers, batch view ops, autorelease pool, retain/release — ~28 symbols) but the implementing C/Obj-C source was never in the repo. The whole native macOS spec lane (make test-macos) failed at link with ~40 undefined symbols, so it was 100% dark — documented as a deferred blocker in docs/initiative-cross-platform-ui/native-compile-matrix.md (macOS row, blocker 2: 'locate or stub the C trampolines'). Adds src/ui/native/collection_bridge.m implementing all the trampolines (MRC / -fno-objc-arc, matching the +0 autoreleased-factory / borrowed-accessor ownership conventions documented in objc_collections.cr) and wires it into the Makefile (COL_BRIDGE_OBJ: compile rule, MACOS_LINK_FLAGS, test-macos dep, clean-bridges). Result: the native lane LINKS. spec/native_macos/native/ (collections_spec — which exercises NSString/NSArray/NSMutableArray/NSDictionary/autorelease — plus the ZStack-alignment geometry spec) now runs 35 examples, 0 failures. Full spec/native_macos/ binary links clean. (The AX/voyager specs still have separate pre-existing GUI/TCC blockers — out of scope here.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… geometry
The "reads a window position, size, and composed frame" example reads a Finder
window via the AX API and asserts width/height > 0. In a headless / no-desktop-
session context (CI, ssh, the validation loop) the app reports a window with
0x0 geometry — there is no on-screen frame — so it hard-FAILED
("Expected 0.0 to be GreaterThan 0.0") instead of pending like the sibling
Voyager AX specs, which already detect "no on-screen AXWindow" and pend.
Guard the same way: pend when the window size is nil or 0x0. In a real desktop
session Finder has a real window (width/height > 0) and the assertions run as
before. Verified: the file now reports 3 examples, 0 failures, 1 pending headless
(was 1 failure).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n trees) App#find / find_all / find_by_id / find_by_id! delegated to Element#find but did NOT forward max_depth, so an App-level search could not be bounded — it always used the default depth-10. Against a large foreign app (e.g. Finder) an UNMATCHED query then walks depth-10 of a very wide tree (millions of cross-process AX reads), which effectively hangs. Surfaced by the native_macos lane: ax_identifier_spec searched Finder for a synthetic (never-matching) id and hung. Plumbed max_depth through the App-level helpers (default 10, backward compatible) so callers probing a big foreign tree can cap the walk, and capped the smoke tests at depth 2 (a bogus id is absent at any depth, so a shallow walk still correctly returns nil / raises). Verified: ax_identifier_spec now runs 5 examples, 0 failures in 205ms (was hanging). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g_showcase
AXTestPatterns::SHARD_ROOT used File.expand_path("../..", __DIR__) from
spec/native_macos/support, which lands at spec/ — not the repo root (three
levels up: support -> native_macos -> spec -> root). So the probe computed
SHOWCASE_BIN = <root>/spec/samples/.../hig_showcase, which never exists, while
macos_visual_spec.cr is own guard checks the CORRECT <root>/samples/.../hig_showcase
(present). The guard therefore passed (file exists) and every visual example then
raised File::NotFoundError launching the wrong path — 126 errors.
This was masked until now: the lane used to hang in ax_test (the unbounded find,
fixed in 8ce6427), so it never reached hig_validation.
Fixed to "../../..". Verified: hig_showcase now launches (real RECT_PX window
geometry printed) and the visual examples render + pass instead of erroring.
(Separate, pre-existing residual now unmasked: the visual lane terminates
abnormally partway through — investigating independently.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arity) Two related Auto Layout fixes that bring the UIKit renderer to parity with the already-correct AppKit renderer, fixing layouts that depend on fill + flex space. 1. ZStack children now fill the container via Auto Layout edge constraints. The visit set an autoresizing MASK (FlexibleWidth|FlexibleHeight) to fill the parent, but every rendered child sets translatesAutoresizingMaskIntoConstraints = NO, so the mask was a dead no-op: a child with no explicit size collapsed to its intrinsic height/position instead of filling. Symptoms: a content VStack's bottom Spacer couldn't expand (a "push to the bottom" tray floated mid-screen), and full-bleed backgrounds only covered part of the frame. Now pin each child's edges to the ZStack exactly as appkit_renderer does — and honor ZStack#alignment (Center/Fill keep the all-edges fill; Leading/Trailing/Top/Bottom pin the aligned edge and soft-fill the opposite via objc_pin_child_aligned, so a drawer panel / toast / badge sits aligned while unconstrained children still fill). 2. Spacer now drops content-hugging to 1 on BOTH axes. The visit's comment always claimed it set a low hugging priority, but the code never did — a plain UIView sat at the default 250, tying with ordinary content under UIStackView .fill, so slack was distributed arbitrarily (a [Spacer, card, Spacer] column could pin the card to the top instead of centering). New objc_set_flex_spacer_priority makes the Spacer unambiguously the flexible element. Verified end-to-end in the happy_coach consumer (iPhone 17 Pro simulator): player transport tray pins to the bottom, auth CTAs sit at the bottom, the mixer bottom sheet anchors correctly, the nav drawer left-anchors, and full-bleed heroes fill — with no regression to macOS (appkit unchanged) and asset_pipeline web specs green (2144 examples, 0 failures). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t parity) In a vertical UIStackView, fill_horizontal was a no-op under any non-Fill alignment: it only lowers content-hugging, which does nothing on the cross axis when alignment is Center/Leading/Trailing — each arranged subview sizes to its intrinsic width. Symptom in the happy_coach consumer: the Sound Levels mixer title (Leading + fill_horizontal inside a Center-aligned sheet column) rendered centered at text width instead of spanning the column. Mirror the appkit fix (34fb2b9): pin each fill_horizontal child's width to the stack at required priority, minus the stack's horizontal layoutMargins so padded containers keep their gutters. Children with explicit width intent (min/max) are skipped, as is the whole loop when fluid_width already pinned every child. Also adds the objc_constrain_equal_width_offset fun binding to the UIKit LibObjCBridge block (the C implementation in objc_bridge.m is shared and already used by appkit). Verified: web suite GREEN (2149 examples, 0 failures, 0 errors); happy_coach consumer on the iPhone 15 simulator — mixer title now left-aligned full-width, player/mixer interaction UITests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…I::View Adds UI::SwipeDirection enum and two gesture APIs to the base View: - on_swipe(direction, &block) backed by Hash(SwipeDirection, Proc(Nil)) - on_long_press property + block convenience setter UIKit: UISwipeGestureRecognizer per direction (cancelsTouchesInView default YES); UILongPressGestureRecognizer (0.5 s, cancelsTouchesInView = NO so child taps still fire). AppKit: NSPanGestureRecognizer with direction-aware CrystalPanSwipeTarget (classifies dominant translation at .ended, 30 pt threshold); NSPressGestureRecognizer for long-press. Web: no-op. ObjC targets are pinned to the host view via objc_setAssociatedObject so Crystal Procs outlive GC scans for the view's lifetime. Continuous pan/drag is explicitly out of scope. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e re-render (Phase 12.D)
A UI::Sheet whose presentation_identity persists across a host's
DESTRUCTIVE re-render (build fresh tree → swap root) now carries its
native hosting view + SwiftKit state handle into the fresh tree, so
SwiftUI never observes a dismissal. This closes the Phase 12.D residual
logged in native_view.cr#dismiss_reactive_presentations! ("the visual
transition is a re-present rather than a dismiss ... proper view
reconciliation would eliminate"). It is why the happy_coach consumer
hand-rolled ZStack overlays instead of UI::Sheet and lost native sheet
UX (grabber, detents, swipe-to-dismiss).
The renderer half (UIKit/AppKit try_reuse state-handle adoption +
reuse_from:/retire_prior!) already landed in 2c0648c; this commit adds:
- NativeView.build_reuse_registry(prior) — the canonical,
platform-agnostic identity→NativeView registry builder. The
renderers' reuse_from: entry and both Voyager hosts now build the
registry ONE way (DRY); the iOS bridge + macOS host are refactored
onto it.
- native_view.cr design commentary updated: the re-present residual
is RESOLVED; the cross-render sweep now only flips ORPHANED handles.
- Web-lane specs (fake-bridge shape) for the platform-agnostic logic:
registry construction, identity matching, nil-identity / non-reactive
/ torn-down pruning, last-render-wins, detach_reused! + reused? +
walk_reactive_views; plus a UI::Sheet state-handle-adoption spec.
- Native macOS spec (spec/native_macos/native/sheet_reuse_spec.cr)
driving the REAL AppKit renderer + linked SwiftKit: a continuing
sheet reuses the prior NativeView (same handle), the fresh UI::Sheet
adopts the surviving state_handle, the
[APIC:Sheet:continuing-presentation-reused] marker fires, an orphaned
sheet is NOT reused, and retire_prior! detaches without
double-release.
v1 content policy: the reuse path keeps the prior shell AND its prior
content subtree (marker content=prior-shell-retained); re-rendering
changed content into a reused shell needs child reconciliation (Stage 3
of the in-place reconciliation design) and is out of scope for v1.
Host-facing API: UI::UIKit::Renderer.new(reuse_from: prior_root) builds
the registry internally; after swapping the fresh root the host calls
renderer.retire_prior!(fresh_root) (detach reused subtree + orphan
sweep). A plain host like happy_coach's render_current no longer needs
the hand-rolled registry/detach/sweep ceremony.
Verified: crystal-alpha spec spec/web/ GREEN (2181 examples, 0 failures,
0 errors, 66 pending). Native macOS sheet_reuse_spec GREEN (4 examples,
marker observed) + zstack/collections regression GREEN (35) — run
individually since test-macos is pre-broken at link. Convention linter:
no new diagnostics (the 2 remaining are pre-existing Complication views).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add `icon_width` and `icon_height` properties to UI::IconButton. When both are set, the Swift facade renders the icon into an exact W×H frame using .fill contentMode + .clipped() (cover-crop), matching Expo's contentFit:"cover" + overflow:"hidden" on sized images. Closes the framework gap required for happy_coach Batch 1 design parity (hamburger 22x18, fixed-box tab icons 24x24). - src/ui/views/icon_button.cr: icon_width/icon_height Float64? properties - src/ui/native/swiftkit_overrides.cr: emit setIconWidth/setIconHeight selectors - IconButtonOverrides.swift: @objc iconWidth/iconHeight NSNumber? properties - IconButtonFacade.swift: useNonSquare branch — .fill + .clipped() into W×H frame - group1_overrides_spec.cr: +2 tests for nil-skip and non-square emission Web suite: 2183 examples, 0 failures. Lint: no new diagnostics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nside fill (B2.1/B2.5) THE BUG (B2.1, reported "more than a dozen times"): a row built as `HStack[Button(fill_horizontal), IconButton]` split the width ~50/50 on iOS instead of label-hugs-left / icon-hugs-right — "Change Password" wrapped to two lines while the chevron sat far right with a huge gap; affirmation ✓/+ and track play-circle rows were spaced the same way. MECHANISM: UIStackView defaults to .fill distribution, which stretches EVERY arranged subview that lacks an explicit width and sits at the same content-hugging priority. A hosted control (UIHostingController-backed Button/IconButton/Image) reports no intrinsic width to the stack's fitting pass, so the no-fill IconButton host stretched alongside the fill Button while the fill Button compressed below its label's intrinsic width and wrapped. NSStackView's default GravityAreas distribution hugs no-width children automatically (appkit reference); UIKit has no equivalent. FIX (framework): - uikit_renderer visit(HStack): after visiting children, pin each non-fill, no-explicit-width child's horizontal hugging + compression resistance to Required so it hugs its intrinsic width (recreating NSStackView's GravityAreas hug). Spacers and fill_horizontal children keep their flex priority. - objc_set_horizontal_fill_priority: also RAISE compression resistance to Required so a fill child grows to absorb slack but never compresses below its intrinsic width (the wrapping "Change Password" root cause). - IconButton visit: pin the host UIView width to the icon's effective box width for a BARE icon (bordered=false), so the chevron/play-circle host hugs its 24/32pt box instead of stretching. Adds UI::IconButton#effective_icon_box_width. FIX (B2.5 ButtonFacade.swift): apply the button's padding to the label content BEFORE the background/clipShape layers (canonical SwiftUI order: content → padding → background → clip). CommonModifiers layered padding OUTSIDE the background, so a filled CTA's pill hugged the text and the padding became dead outer margin (Home "Listen now": "wrapping right to the text with no margin"). Padding is nulled on the shadowed overrides so CommonModifiers does not double-apply it. Web suite: 2186 examples, 0 failures (baseline 2183 + 3 new IconButton specs). Lint: no new diagnostics. The HStack/host-pin constraint calls are exercised only under -Dios (sim); verified on-device via account_settings AX frame dump (label hugs intrinsic 219pt single line, chevron right-pinned at 32pt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
render_document emitted no base reset, so every web render inherited the browser
default 8px body margin and a serif (Times) fallback for any element without its
own font — making web renders deviate from the native platforms for ALL apps.
inject_theme_css now emits html,body{margin:0;padding:0} and a sans-serif base
font. Proven via tag-for-tag DOM diff: a Crystal screen that deviated (11 nodes
on Times) now matches its golden 100%. App typography is still set per-view.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…native/golden These are renderer-wide correctness fixes (benefit every consuming app), discovered while pixel-matching a screen against its react-native-web golden and the AppKit render (which were already correct). 1. fill_horizontal now picks the right CSS for the parent's flex axis. It unconditionally emitted `flex: 1 1 0%`, which only grows the MAIN axis. In a VStack (column) the main axis is vertical, so a fill_horizontal child never stretched to full WIDTH and — with the stack's default `align-items: center` — shrink-wrapped and centered instead of left-aligning full-width. Now: row parent keeps `flex: 1 1 0%`; column parent gets `align-self: stretch`; unknown parents keep the historical behavior (no regression). Parent axis is read from the parent element's own emitted style, so there is no parallel stack to desync. This is why headings/cards rendered centered on web but left-aligned on AppKit. 2. An explicit pure-black Label color is no longer swapped for a theme token. color_css treats Color(0,0,0) as "unset" and substitutes var(--ap-color-text-primary) when a default_token is supplied. But a Label whose text_color_role is nil has EXPLICITLY opted into a raw RGBA (the text_color= setter nulls the role), so the Label visit now honors it verbatim. Previously deliberate black body text vanished to the near-white primary on dark themes. 3. font-family is quoted. Unquoted multi-word names (e.g. "Helvetica Neue", "Alegreya Sans_medium") are invalid CSS and silently fall back to the base sans-serif, changing glyph metrics and text bounds. Quotes are valid around single-token names too, so this is lossless for existing hyphenated families. Note: the gitignored lib/ build copy in consuming apps is synced separately; this commit covers the asset_pipeline source of truth only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…button text-align) Found while driving the Expo OnboardingAffirmationSelec golden to pixel-parity; all three are generic renderer correctness fixes that benefit every app. 1. box-sizing: border-box global reset. Padding/border now live INSIDE an element's declared width/height, matching the native layout engines and react-native-web (the golden). Without it, a view with explicit width + padding overflowed by the padding and centered children landed off-center (the affirmation column rendered +10px right of the golden everywhere). 2. enforce_touch_target no longer SHRINKS a larger explicit min. The 44px floor ran after apply_common_styles and, via CSS last-declaration-wins, clobbered an author's explicit min-width/height (a 334px CTA collapsed to ~89px content width). Now honors max(floor, explicit). Stale TextArea spec (asserted the clobbered 44 over its real 80px min-height) updated. 3. UI::Button gains text_alignment (default Center = the browser <button> default, so existing output is byte-identical). Content buttons whose label is body text (wrapping affirmation/thought cards) can now read left-aligned, matching the golden's left-aligned Pressable text. Full web spec suite green (2186 examples, 0 failures).
macOS Forms default to .columns, which right-aligns labels into a separate column that clips at narrow window widths and drops grouped section chrome entirely. .grouped is the System Settings look and matches Form's default rendering on iOS. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EkCaEC49RRReKtSTXgcppT
…d view apply_common_properties already applied a View's background to its CALayer but ignored corner_radius / border_width / border_color, so a plain VStack/HStack surface could not be rounded or rimmed without reaching for a shape widget. Add the missing layer calls, gated on non-default values so views that set neither are byte-for-byte unchanged. Enables token-driven glass panes, rounded chat bubbles, and neutral rims for the LocalChat Glasswork skin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UGEzDn8wq6CvXvCYceoX3L
Adds the shared primitives a scrolling, resizable content surface needs: - objc_bridge.m: objc_set_vertical_fill_priority (claim a stack's leftover vertical space, reflow on resize), nsscrollview_scroll_to_end, and nsscrollview_is_at_bottom (stick-to-bottom while streaming, release on scroll-up). - ScrollView: fill_vertical property, native_handle, and scroll_to_end / at_bottom? helpers driving the new C fns after render (no tree rebuild). - appkit_renderer: honor fill_vertical (skip fixed height, low vertical hugging) and store the NSScrollView pointer on the view. These are exactly the primitives the marketplace catalog scroll + search need. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UGEzDn8wq6CvXvCYceoX3L
Mirrors SearchField#on_submit. Adds on_submit to UI::TextField, a submitToken NSNumber on TextFieldOverrides, and wires SwiftUI .onSubmit in TextFieldFacade to fire the string-valued trampoline with the current text on bare Return. The renderer registers the handler on the string channel and threads the token through the overrides (no C-selector change). Backward compatible: nil on_submit keeps the prior behavior (Return does nothing). Swift static lib rebuilt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UGEzDn8wq6CvXvCYceoX3L
…illEqually Harvested from the happy_coach demo-shell consumer (2026-07-23 dead-tap hunt): - View#touch_passthrough — informational overlays opt out of hit-testing (UIKit userInteractionEnabled=false). A full-screen overlay otherwise swallows every touch natively even though clicks pass through on web. - AsyncImage#preloaded_data + UIKit visit: no async URL loader exists on the native path, so pre-fetched bytes decode synchronously (base64 -> NSData -> UIImage), aspect-fill, clipped, frame pinned via constraints (a UIImageView's intrinsic size is the bitmap's). - UIKit HStack: honor fill_equally (UIStackViewDistribution.fillEqually) — was appkit-only; iOS tab bars crammed cells to intrinsic width. - Web AsyncImage: size the <img> to its container (width/height 100%) so object-fit governs the crop; a raw <img> rendered at natural size.
…taps A Card rendered via apsk_make_card hosts its content inside a SwiftUI VStack (APSKHostedChild / UIViewRepresentable) within a UIHostingController. Any interactive child (Button, IconButton, ...) is itself a UIHostingController, so it becomes a hosting controller NESTED inside the Card's. Across that second SwiftUI hosting boundary the child's action never fires: the child VC parents correctly (Path A) and the button is hit-testable, but the outer hosting layer's gesture arbitration swallows the touch. A button INSIDE a Card was therefore dead on iOS. Route visit(UI::Card) to a raw-UIKit body (rename of the retained _legacy_card -> render_uikit_card) that adds the content through the normal visit path. The interactive child is then a direct UIStackView arranged subview whose own hosting controller's responder chain reaches the root view controller, so taps fire exactly like a button in a plain HStack. The old SwiftUI body is retained as _swiftui_card for reference / non-iOS. Also renders the Card's explicit background/corner/border verbatim via apply_common_properties instead of layering .regularMaterial over the requested colour. Proven by HappyCoach's testAudioPlayerPlayPauseSmoke (play-circle inside a tracks Card was dead; now fires and opens the player).
# Conflicts: # spec/test_js/some_js.js # spec/web/components/phase2_verification_spec.cr # spec/web/ui/views_spec.cr
Member
Author
|
Superseded by #14, rebuilt from current main so the 0.37.0 release contains only the static-asset compiler and its CI coverage. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Why
Amber V2 applications need one documented production asset contract instead of unversioned public files, incomplete font/image handling, and destructive output cleanup.
Validation