Phase 16 — UI polish: from "functional" to "amazing" #2

Closed
opened 2026-04-30 21:09:12 +00:00 by mik-tf · 3 comments
Owner

Phase 16 — UI polish: from "functional" to "amazing"

Context

Phase 15-release shipped v0.2.0-level functionality but the UI is bare-minimum. The hand-rolled ~6 KiB CSS subset (crates/hero_assistance_ui_wasm/assets/app_overrides.css) was a fix-now to unblock the v0.2.0 walkthrough — it makes the components readable, but the result is cramped, lacks polish, and doesn't take advantage of the dioxus-bootstrap-css typed-component library that's already in our Cargo.toml.

This issue tracks the focused UI polish session that brings the embed up to "amazing" quality.

Pre-Phase-16 baseline screenshots: see tests/baselines/desktop-{empty,acme,detail,globex,globex-error}.png from session 23.

Pre-session investigation (~15 min)

Before writing any UI code, settle the foundation. Multiple attempts in session 23 failed:

  • dioxus_bootstrap_css::BootstrapHead (uses asset!()) silently failed under plain cargo build — works only with dx build
  • <link rel="stylesheet"> with jsDelivr CDN URL broke webkit2gtk render entirely
  • 233 KiB inline <style>{include_str!()}</style> of full Bootstrap broke Dioxus rsx text-content rendering (likely size limit or curly-brace parser quirk)

Need to evaluate and pick one:

  • dx build asset pipeline — handles asset!() properly via the manganis runtime. Restructure _app's build to use dx bundle instead of cargo build. May affect packaging/CI.
  • wry's register_uri_scheme_protocol — register a custom URL scheme like assist:// that resolves to embedded resources via include_bytes!(). Bypasses asset pipelines entirely. Probably the cleanest path; one-time wiring in _app's launcher.
  • Multiple smaller <style> chunks — split bootstrap.min.css into ~50 KiB chunks. Quick hack if size was the issue.
  • Self-hosted file dropped at runtime to a known path_app extracts bundled CSS to ~/.cache/hero_assistance_app/assets/bootstrap.min.css on first run, references via <link href="file://...">.

Pick whichever the investigation says is least risky. wry::WebViewBuilder::with_custom_protocol is the leading candidate.

Implementation

1. Foundation: ship full Bootstrap 5.3.3 reliably to both shells

  • Land the chosen mechanism from the investigation.
  • Drop or shrink app_overrides.css to ~30 lines of _app-specific palette tweaks (the dark-mode color overrides matching _app/src/app.css's left-panel palette).
  • Verify both cargo build -p hero_assistance_app --release AND cargo check -p hero_assistance_ui_wasm --target wasm32-unknown-unknown stay green.
  • Verify the standalone SPA (_ui --dist) still loads styles correctly — currently it pulls Bootstrap via askama static paths from _ui_wasm/index.html head; this should be unified.

2. Layout overhaul (the cramped feel)

  • Right pane: replace .container (max-width 720px) with .container-fluid so content uses available width minus reasonable side padding.
  • Ticket detail page: proper 2-column responsive layout — <Row><Col xl={8}>thread + composer</Col><Col xl={4}>sidebars</Col></Row>. Stacks on small screens, side-by-side on large.
  • Sticky composer pinned to bottom of viewport (so on long threads, the composer is always visible without scrolling).
  • Scrollable comment thread: max-height = viewport minus header/composer; auto-scroll to bottom on new message arrival via ScrollIntoView.
  • Increase global padding/spacing — use Bootstrap's gap-*, mb-*, mt-* utilities consistently.

3. Migrate components to dioxus-bootstrap-css typed RSX

The crate (~/.cargo/git/checkouts/dioxus-bootstrap-*/.../crates/dioxus-bootstrap/src/) ships Card, Button, Container, Row, Col, Modal, Toast, Alert, Badge, ListGroup, Form, InputGroup, Nav, Tabs, Spinner, Placeholder, Pagination, Breadcrumb, etc. — all already on disk via the existing dependency.

  • crates/hero_assistance_ui_wasm/src/components/tickets/list.rs — replace raw class="card" / class="list-group-item" with Card { body: ... } and ListGroup / ListGroupItem.
  • crates/hero_assistance_ui_wasm/src/components/tickets/detail.rs — same migration; the sidebars become Cards with proper header/body slots.
  • crates/hero_assistance_ui_wasm/src/components/tickets/composer.rs — replace raw <form>/<textarea>/<button> with Form/InputGroup/Button.
  • crates/hero_assistance_ui_wasm/src/components/enrollment.rsCard for the sign-in card; Alert { color: Color::Danger } for the validation/InvalidOrExpired error states (replaces the inline is-invalid text); Spinner for the busy state.
  • New ticket modal — currently inline <form> in list.rs. Migrate to Modal { title: "Create ticket", show: signal, ... }.
  • Project-config overlay — currently bespoke fullscreen overlay in _app/src/project_config.rs. Migrate to Modal { size: ModalSize::Lg, scrollable: true, ... }.
  • Tab strip — currently _app/src/app.css custom (.tab-strip / .tab-pane). Migrate to Nav { variant: NavVariant::Tabs } + TabContent.

4. Visual polish

  • Bootstrap Icons — properly bundled (replaces ASCII fallbacks in app_overrides.css). Reference via the icon classes (bi bi-send-fill, bi bi-x-lg, bi bi-arrow-left, bi bi-gear-fill, bi bi-plus-lg, bi bi-bell, etc.).
  • Avatar circles — ~32px circles with user initials and a deterministic color from user_id. Render in comment author area, presence sidebars.
  • Status indicators — small filled circle next to user names in presence sidebar (green = online, gray = offline). Respond to presence.update events.
  • Subtle animations — fade-in (@keyframes, ~150ms) on new comment arrivals; smooth transform on tab switches; scale-in on modal open.
  • Typography hierarchy — consistent ticket title size (.fs-3 or .h3), comment author bold + timestamp muted small + content body. Avoid the current "everything looks the same weight" feel.
  • Color palette tweaks — semantic colors via Bootstrap variables: ticket status uses bg-success-subtle (open) / bg-secondary-subtle (archived); comment row borders use border-subtle.

5. Empty / loading / error states

  • Skeleton placeholders during initial fetches — Placeholder from dioxus-bootstrap-css. Show on first comment.list / ticket.list while resolving.
  • Empty list states — "No tickets yet — file the first one!" CTA centered when ticket.list returns empty. "Be the first to comment" in empty thread.
  • Toast on enrollment successToast { color: Color::Success } "Welcome, Alice!" auto-dismissing after 3s. Replaces the current " Signed in" box.
  • Error toasts on failed RPCs — currently silent fail. Catch RpcCallError + render dismissible Toast { color: Color::Danger }.
  • Loading state for composer send — disable button + show inline Spinner while server resolves.

6. _app-specific polish

  • Window title — set to "Hero Assistance" (currently "Dioxus App" placeholder) via dioxus::desktop::Config::new().with_window(WindowBuilder::new().with_title("Hero Assistance")).
  • Window icon — embed an icon (the existing favicon SVG can be rasterized) via WindowBuilder::with_window_icon.
  • _app shell uses Bootstrap for the left panel too — currently _app/src/app.css is hand-rolled. Migrate #left-panel styles to Bootstrap utilities (bg-body-tertiary, border-end, etc.) so the left panel matches the right pane's style.
  • Lifecycle breadcrumb logstracing::info! calls at: window-open, per-tab transport-resolved, per-tab driver-spawned, project-config-overlay-open. Helps debuggability.
  • D-17 live restart UX — when subscription list mutates via the overlay, show a brief Toast "Tab globex added" / "Tab acme removed" to make the live-restart visible.

7. Re-capture all baselines + visual diff

  • Re-capture tests/baselines/desktop-{empty,acme,detail,globex,globex-error}.png after polish lands.
  • Side-by-side comparison check-in (e.g. a desktop-before-after.md doc) so reviewers can see the improvement.
  • Optional: revisit perceptual-diff CI (Phase 9b's <1% policy) — though desktop pixel-diffing is hard without a virtual display harness (per L-07-style limitation).

Out of scope for Phase 16

  • CDP-equivalent automation for _app (different problem; stays on roadmap).
  • Mobile-responsive layout (Phase 17+ if there's a mobile target).
  • Custom theming beyond default Bootstrap dark.
  • L-04 (users table UNIQUE(email) + display_name) — touches schema; separate session.
  • D-10 cleanup (drop askama_templates) — separate session.

Success criteria

  • _app desktop window looks coherent, spacious, professional. Indistinguishable from a "this team has UX designers" product.
  • Standalone SPA looks the same (component graph is identical).
  • All 5 desktop screenshots re-captured + visibly improved.
  • No regression in the validated behavior from session 23 (live updates, cross-project isolation, optimistic insert, presence, etc.).
  • Both shells continue to ship from a single _ui_wasm::App codebase.
  • Polish session takes 1-2 dedicated sessions of work; cargo build time stays under ~30s post-cold-cache.

Why this matters

D-09 §"Argument" pt 2 makes embed-as-Support-tab the primary deployment of hero_assistance — meaning the UI quality has to hold up not just inside _app, but as a tab inside other Hero project UIs (freezone admin, TFGrid console, Hero OS shell). Functional-but-lame is fine for a v0.1 backend-complete release; it's not fine for the customer-facing surface that defines the product.

References

  • Master tracker: forge.ourworld.tf/lhumina_code/hero_assistance/issues/1
  • Session 23 findings: runs/phase15_findings.md (recorded both fix-now items + 10 deferred to Phase 16+)
  • Pre-Phase-16 baselines: tests/baselines/desktop-*.png
  • D-09 (UI architecture commitment): decisions/D-09-dioxus-ui-adoption.md
  • D-13 (_app is dioxus desktop): decisions/D-13-app-shell-dioxus-desktop.md
  • D-17 (config persistence): decisions/D-17-config-persistence-layer.md
  • dioxus-bootstrap-css crate: ~/.cargo/git/checkouts/dioxus-bootstrap-af2c6a1d42b4a423/.../crates/dioxus-bootstrap/
# Phase 16 — UI polish: from "functional" to "amazing" ## Context Phase 15-release shipped `v0.2.0`-level functionality but the UI is bare-minimum. The hand-rolled ~6 KiB CSS subset (`crates/hero_assistance_ui_wasm/assets/app_overrides.css`) was a fix-now to unblock the v0.2.0 walkthrough — it makes the components readable, but the result is cramped, lacks polish, and doesn't take advantage of the `dioxus-bootstrap-css` typed-component library that's already in our `Cargo.toml`. This issue tracks the focused UI polish session that brings the embed up to "amazing" quality. **Pre-Phase-16 baseline screenshots:** see `tests/baselines/desktop-{empty,acme,detail,globex,globex-error}.png` from session 23. ## Pre-session investigation (~15 min) Before writing any UI code, settle the foundation. Multiple attempts in session 23 failed: - `dioxus_bootstrap_css::BootstrapHead` (uses `asset!()`) silently failed under plain `cargo build` — works only with `dx build` - `<link rel="stylesheet">` with jsDelivr CDN URL broke webkit2gtk render entirely - 233 KiB inline `<style>{include_str!()}</style>` of full Bootstrap broke Dioxus rsx text-content rendering (likely size limit or curly-brace parser quirk) Need to evaluate and pick one: - [ ] **`dx build` asset pipeline** — handles `asset!()` properly via the `manganis` runtime. Restructure `_app`'s build to use `dx bundle` instead of `cargo build`. May affect packaging/CI. - [ ] **`wry`'s `register_uri_scheme_protocol`** — register a custom URL scheme like `assist://` that resolves to embedded resources via `include_bytes!()`. Bypasses asset pipelines entirely. Probably the cleanest path; one-time wiring in `_app`'s launcher. - [ ] **Multiple smaller `<style>` chunks** — split bootstrap.min.css into ~50 KiB chunks. Quick hack if size was the issue. - [ ] **Self-hosted file dropped at runtime to a known path** — `_app` extracts bundled CSS to `~/.cache/hero_assistance_app/assets/bootstrap.min.css` on first run, references via `<link href="file://...">`. Pick whichever the investigation says is least risky. **`wry::WebViewBuilder::with_custom_protocol`** is the leading candidate. ## Implementation ### 1. Foundation: ship full Bootstrap 5.3.3 reliably to both shells - [ ] Land the chosen mechanism from the investigation. - [ ] Drop or shrink `app_overrides.css` to ~30 lines of `_app`-specific palette tweaks (the dark-mode color overrides matching `_app/src/app.css`'s left-panel palette). - [ ] Verify both `cargo build -p hero_assistance_app --release` AND `cargo check -p hero_assistance_ui_wasm --target wasm32-unknown-unknown` stay green. - [ ] Verify the standalone SPA (`_ui --dist`) still loads styles correctly — currently it pulls Bootstrap via askama static paths from `_ui_wasm/index.html` head; this should be unified. ### 2. Layout overhaul (the cramped feel) - [ ] Right pane: replace `.container` (max-width 720px) with `.container-fluid` so content uses available width minus reasonable side padding. - [ ] Ticket detail page: proper 2-column responsive layout — `<Row><Col xl={8}>thread + composer</Col><Col xl={4}>sidebars</Col></Row>`. Stacks on small screens, side-by-side on large. - [ ] Sticky composer pinned to bottom of viewport (so on long threads, the composer is always visible without scrolling). - [ ] Scrollable comment thread: max-height = viewport minus header/composer; auto-scroll to bottom on new message arrival via `ScrollIntoView`. - [ ] Increase global padding/spacing — use Bootstrap's `gap-*`, `mb-*`, `mt-*` utilities consistently. ### 3. Migrate components to `dioxus-bootstrap-css` typed RSX The crate (`~/.cargo/git/checkouts/dioxus-bootstrap-*/.../crates/dioxus-bootstrap/src/`) ships `Card`, `Button`, `Container`, `Row`, `Col`, `Modal`, `Toast`, `Alert`, `Badge`, `ListGroup`, `Form`, `InputGroup`, `Nav`, `Tabs`, `Spinner`, `Placeholder`, `Pagination`, `Breadcrumb`, etc. — **all already on disk via the existing dependency.** - [ ] `crates/hero_assistance_ui_wasm/src/components/tickets/list.rs` — replace raw `class="card"` / `class="list-group-item"` with `Card { body: ... }` and `ListGroup` / `ListGroupItem`. - [ ] `crates/hero_assistance_ui_wasm/src/components/tickets/detail.rs` — same migration; the sidebars become `Card`s with proper `header`/`body` slots. - [ ] `crates/hero_assistance_ui_wasm/src/components/tickets/composer.rs` — replace raw `<form>`/`<textarea>`/`<button>` with `Form`/`InputGroup`/`Button`. - [ ] `crates/hero_assistance_ui_wasm/src/components/enrollment.rs` — `Card` for the sign-in card; `Alert { color: Color::Danger }` for the validation/InvalidOrExpired error states (replaces the inline `is-invalid` text); `Spinner` for the busy state. - [ ] **New ticket modal** — currently inline `<form>` in `list.rs`. Migrate to `Modal { title: "Create ticket", show: signal, ... }`. - [ ] **Project-config overlay** — currently bespoke fullscreen overlay in `_app/src/project_config.rs`. Migrate to `Modal { size: ModalSize::Lg, scrollable: true, ... }`. - [ ] **Tab strip** — currently `_app/src/app.css` custom (`.tab-strip` / `.tab-pane`). Migrate to `Nav { variant: NavVariant::Tabs }` + `TabContent`. ### 4. Visual polish - [ ] **Bootstrap Icons** — properly bundled (replaces ASCII fallbacks `→` `✕` in `app_overrides.css`). Reference via the icon classes (`bi bi-send-fill`, `bi bi-x-lg`, `bi bi-arrow-left`, `bi bi-gear-fill`, `bi bi-plus-lg`, `bi bi-bell`, etc.). - [ ] **Avatar circles** — ~32px circles with user initials and a deterministic color from `user_id`. Render in comment author area, presence sidebars. - [ ] **Status indicators** — small filled circle next to user names in presence sidebar (green = online, gray = offline). Respond to `presence.update` events. - [ ] **Subtle animations** — fade-in (`@keyframes`, ~150ms) on new comment arrivals; smooth `transform` on tab switches; scale-in on modal open. - [ ] **Typography hierarchy** — consistent ticket title size (`.fs-3` or `.h3`), comment author bold + timestamp muted small + content body. Avoid the current "everything looks the same weight" feel. - [ ] **Color palette tweaks** — semantic colors via Bootstrap variables: ticket status uses `bg-success-subtle` (open) / `bg-secondary-subtle` (archived); comment row borders use `border-subtle`. ### 5. Empty / loading / error states - [ ] **Skeleton placeholders** during initial fetches — `Placeholder` from dioxus-bootstrap-css. Show on first `comment.list` / `ticket.list` while resolving. - [ ] **Empty list states** — "No tickets yet — file the first one!" CTA centered when `ticket.list` returns empty. "Be the first to comment" in empty thread. - [ ] **Toast on enrollment success** — `Toast { color: Color::Success }` "Welcome, Alice!" auto-dismissing after 3s. Replaces the current "✅ Signed in" box. - [ ] **Error toasts on failed RPCs** — currently silent fail. Catch RpcCallError + render dismissible `Toast { color: Color::Danger }`. - [ ] **Loading state for composer send** — disable button + show inline `Spinner` while server resolves. ### 6. `_app`-specific polish - [ ] **Window title** — set to "Hero Assistance" (currently "Dioxus App" placeholder) via `dioxus::desktop::Config::new().with_window(WindowBuilder::new().with_title("Hero Assistance"))`. - [ ] **Window icon** — embed an icon (the existing favicon SVG can be rasterized) via `WindowBuilder::with_window_icon`. - [ ] **`_app` shell uses Bootstrap** for the left panel too — currently `_app/src/app.css` is hand-rolled. Migrate `#left-panel` styles to Bootstrap utilities (`bg-body-tertiary`, `border-end`, etc.) so the left panel matches the right pane's style. - [ ] **Lifecycle breadcrumb logs** — `tracing::info!` calls at: window-open, per-tab transport-resolved, per-tab driver-spawned, project-config-overlay-open. Helps debuggability. - [ ] **D-17 live restart UX** — when subscription list mutates via the overlay, show a brief Toast "Tab globex added" / "Tab acme removed" to make the live-restart visible. ### 7. Re-capture all baselines + visual diff - [ ] Re-capture `tests/baselines/desktop-{empty,acme,detail,globex,globex-error}.png` after polish lands. - [ ] Side-by-side comparison check-in (e.g. a `desktop-before-after.md` doc) so reviewers can see the improvement. - [ ] Optional: revisit perceptual-diff CI (Phase 9b's `<1%` policy) — though desktop pixel-diffing is hard without a virtual display harness (per L-07-style limitation). ## Out of scope for Phase 16 - CDP-equivalent automation for `_app` (different problem; stays on roadmap). - Mobile-responsive layout (Phase 17+ if there's a mobile target). - Custom theming beyond default Bootstrap dark. - L-04 (users table `UNIQUE(email)` + `display_name`) — touches schema; separate session. - D-10 cleanup (drop `askama_templates`) — separate session. ## Success criteria - `_app` desktop window looks coherent, spacious, professional. Indistinguishable from a "this team has UX designers" product. - Standalone SPA looks the same (component graph is identical). - All 5 desktop screenshots re-captured + visibly improved. - No regression in the validated behavior from session 23 (live updates, cross-project isolation, optimistic insert, presence, etc.). - Both shells continue to ship from a single `_ui_wasm::App` codebase. - Polish session takes 1-2 dedicated sessions of work; `cargo build` time stays under ~30s post-cold-cache. ## Why this matters D-09 §"Argument" pt 2 makes embed-as-Support-tab the *primary* deployment of `hero_assistance` — meaning the UI quality has to hold up not just inside `_app`, but as a tab inside other Hero project UIs (freezone admin, TFGrid console, Hero OS shell). Functional-but-lame is fine for a v0.1 backend-complete release; it's not fine for the customer-facing surface that defines the product. ## References - Master tracker: forge.ourworld.tf/lhumina_code/hero_assistance/issues/1 - Session 23 findings: `runs/phase15_findings.md` (recorded both fix-now items + 10 deferred to Phase 16+) - Pre-Phase-16 baselines: `tests/baselines/desktop-*.png` - D-09 (UI architecture commitment): `decisions/D-09-dioxus-ui-adoption.md` - D-13 (`_app` is dioxus desktop): `decisions/D-13-app-shell-dioxus-desktop.md` - D-17 (config persistence): `decisions/D-17-config-persistence-layer.md` - dioxus-bootstrap-css crate: `~/.cargo/git/checkouts/dioxus-bootstrap-af2c6a1d42b4a423/.../crates/dioxus-bootstrap/`
Author
Owner

Session 27 (2026-05-01) — Phase 16b-impl complete

Commits on development:

  • c9c1cacfeat(_ui_wasm,_app)(session-27): Phase 16b-impl visual polish surface (15 files, +477/-389)
  • ae5ca9achore(sessions): record session-27 manifest + CLAUDE.md/prompt.md handoff (3 files, +152/-34)

What shipped (8 of 12 §3 work items)

# Item Outcome
W1 Audit-log entry Appended at top of docs/dev/e2e_checklist.md.
W2 Replace app_overrides.css with typed components → Delete orphan Mid-session investigation confirmed crates/hero_assistance_ui_wasm/assets/app_overrides.css (287 LoC) was orphan dead code: never <link>-ed, never include_bytes!-ed, never served via assist://, no .hassist-app-root wrapper rendered in any DOM. Full Bootstrap CSS at assist://bootstrap.min.css (228 KiB) handles all rendering already. Lossless cleanup, zero pixel change.
W3 Layout polish StatusBadge migrated to typed Badge; orphan .badge-{pending,ok,err} rules dropped from app.css; EmptyTabs gains bi-collection display-4 icon; event-list row gets text-overflow: ellipsis for long project names.
W4 Animations Bootstrap typed Collapse wraps the project-config Modal's "Add subscription" form (chevron-right ↔ chevron-down toggle; auto-expand when subs is empty). 150ms opacity ease-in-out fade on .app-tab-pane swaps.
W5 Toast helper New crates/hero_assistance_ui_wasm/src/components/toast.rs (~140 LoC + 1 unit test). RpcErrorToast + DEFAULT_AUTO_DISMISS_MS = 5_000; gloo_timers::callback::Timeout on wasm32; pinnable. Wired at tickets/composer.rs (send-side errors) + enrollment.rs Email/Token banner-class errors. Inline Alert kept for input-tied validation.
W6 Placeholder skeletons New TicketListSkeleton (tickets/list.rs) + CommentListSkeleton (tickets/detail.rs) using typed Placeholder + PlaceholderParagraph glow animations; aria-busy=true. CommentList gained a loading: bool prop to distinguish "loading" from "real-empty".
W7 Window icon New crates/hero_assistance_ui_wasm/assets/icon.png (377-byte 64×64 RGBA speech-bubble glyph; PIL-generated from the index.html SVG). WINDOW_ICON_PNG byte-const. _app/src/main.rs::load_window_icon decodes via png crate (new dep, 0.17, pure-Rust). WindowBuilder::with_window_icon applied. assist://icon.png slot for embedded webview link tags.
W8 Breadcrumb Typed Breadcrumb { Home / Tickets / <ticket-name> } on tickets/detail.rs replaces the prior ad-hoc back-arrow link. Single-affordance navigation.
W9 Re-capture baselines Deferred — askama-rendered /admin /, /editor pages are inherited from hero_collab and untouched in 16b; recapture would reproduce identical PNGs. Desktop captures are part of Phase 16b-release.
W10 Verify-block extension +24 grep assertions in prompt.md §3 (297 → 321 passing).
W11 Row-state flips M1-H-15 (window-icon row) flipped from [skip: M2] to Schema? ✅. Section H summary updated: 15 M1, no more M2 deferrals.
W12 Audit-log close This comment + handoff bookkeeping (sessions/27.yml + CLAUDE.md AUTOGEN block + prompt.md §1/§3 rewrites).

Test posture

  • 205 native passing (unchanged) / 32 _ui_wasm lib passing (+1 from W5 unit test phase16b_default_auto_dismiss_ms_is_five_seconds) / 3 L-03 unchanged / 1 phase10 transient flake / 8 ignored.
  • Kickstart 321/321 verify assertions passing.

Decisions and limitations

  • No new decisions. Typed-component additions sit on the existing AppProps prop contract per D-09; no new feature gates; no assist:// shape change.
  • No new limitations.
  • B.5 skip held — none of the touched surfaces are component-contract or assist:// protocol-shape changes.

What's next

v0.2.0 tag is the Rule-6 user-driven gate — Phase 16b-release, the agent-half / user-half split that worked for Phase 13a/13b and Phase 15-release. The user clicks through the 4-port demo against the new desktop chrome (collapse animation in project-config Modal; tab-content fade; window icon in title bar; status badge typed migration; empty-tabs icon polish) + SPA polish (RpcErrorToast on send failures; Placeholder skeletons during ticket-list and comment-list initial fetches; Breadcrumb on ticket detail), captures 5 fresh desktop PNGs per tests/baselines/desktop-README.md, stamps Section H + F + M Human? cells in docs/dev/e2e_checklist.md, then git tag v0.2.0 on development and pushes. Estimated ~1 session.

Full next-session entry: prompt.md §3 — Candidate A (Phase 16b-release; this issue's closure path) OR Candidate B (Phase 18c Playwright suite, #3; ~2–3 sessions; B.5 runs).

## Session 27 (2026-05-01) — Phase 16b-impl complete **Commits on `development`:** - [`c9c1cac`](https://forge.ourworld.tf/lhumina_code/hero_assistance/commit/c9c1cac) — `feat(_ui_wasm,_app)(session-27): Phase 16b-impl visual polish surface` (15 files, +477/-389) - [`ae5ca9a`](https://forge.ourworld.tf/lhumina_code/hero_assistance/commit/ae5ca9a) — `chore(sessions): record session-27 manifest + CLAUDE.md/prompt.md handoff` (3 files, +152/-34) ### What shipped (8 of 12 §3 work items) | # | Item | Outcome | |---|---|---| | W1 | Audit-log entry | Appended at top of `docs/dev/e2e_checklist.md`. | | W2 | ~~Replace `app_overrides.css` with typed components~~ → Delete orphan | Mid-session investigation confirmed `crates/hero_assistance_ui_wasm/assets/app_overrides.css` (287 LoC) was orphan dead code: never `<link>`-ed, never `include_bytes!`-ed, never served via `assist://`, no `.hassist-app-root` wrapper rendered in any DOM. Full Bootstrap CSS at `assist://bootstrap.min.css` (228 KiB) handles all rendering already. **Lossless cleanup, zero pixel change.** | | W3 | Layout polish | `StatusBadge` migrated to typed `Badge`; orphan `.badge-{pending,ok,err}` rules dropped from `app.css`; `EmptyTabs` gains `bi-collection display-4` icon; `event-list` row gets `text-overflow: ellipsis` for long project names. | | W4 | Animations | Bootstrap typed `Collapse` wraps the project-config Modal's "Add subscription" form (chevron-right ↔ chevron-down toggle; auto-expand when subs is empty). 150ms `opacity` ease-in-out fade on `.app-tab-pane` swaps. | | W5 | Toast helper | New `crates/hero_assistance_ui_wasm/src/components/toast.rs` (~140 LoC + 1 unit test). `RpcErrorToast` + `DEFAULT_AUTO_DISMISS_MS = 5_000`; `gloo_timers::callback::Timeout` on wasm32; pinnable. Wired at `tickets/composer.rs` (send-side errors) + `enrollment.rs` Email/Token banner-class errors. Inline `Alert` kept for input-tied validation. | | W6 | Placeholder skeletons | New `TicketListSkeleton` (`tickets/list.rs`) + `CommentListSkeleton` (`tickets/detail.rs`) using typed `Placeholder` + `PlaceholderParagraph` glow animations; `aria-busy=true`. `CommentList` gained a `loading: bool` prop to distinguish "loading" from "real-empty". | | W7 | Window icon | New `crates/hero_assistance_ui_wasm/assets/icon.png` (377-byte 64×64 RGBA speech-bubble glyph; PIL-generated from the `index.html` SVG). `WINDOW_ICON_PNG` byte-const. `_app/src/main.rs::load_window_icon` decodes via `png` crate (new dep, 0.17, pure-Rust). `WindowBuilder::with_window_icon` applied. `assist://icon.png` slot for embedded webview link tags. | | W8 | Breadcrumb | Typed `Breadcrumb { Home / Tickets / <ticket-name> }` on `tickets/detail.rs` replaces the prior ad-hoc back-arrow link. Single-affordance navigation. | | W9 | Re-capture baselines | **Deferred** — askama-rendered `/admin /, /editor` pages are inherited from `hero_collab` and untouched in 16b; recapture would reproduce identical PNGs. Desktop captures are part of Phase 16b-release. | | W10 | Verify-block extension | +24 grep assertions in `prompt.md` §3 (297 → **321 passing**). | | W11 | Row-state flips | `M1-H-15` (window-icon row) flipped from `[skip: M2]` to `Schema? ✅`. Section H summary updated: 15 M1, no more M2 deferrals. | | W12 | Audit-log close | This comment + handoff bookkeeping (`sessions/27.yml` + CLAUDE.md AUTOGEN block + prompt.md §1/§3 rewrites). | ### Test posture - **205 native passing** (unchanged) / **32 `_ui_wasm` lib passing** (+1 from W5 unit test `phase16b_default_auto_dismiss_ms_is_five_seconds`) / 3 L-03 unchanged / 1 phase10 transient flake / 8 ignored. - Kickstart **321/321 verify assertions passing**. ### Decisions and limitations - **No new decisions.** Typed-component additions sit on the existing `AppProps` prop contract per [D-09](https://forge.ourworld.tf/lhumina_code/hero_assistance/src/branch/development/decisions/D-09-dioxus-ui-adoption.md); no new feature gates; no `assist://` shape change. - **No new limitations.** - **B.5 skip held** — none of the touched surfaces are component-contract or `assist://` protocol-shape changes. ### What's next **v0.2.0 tag is the Rule-6 user-driven gate** — Phase 16b-release, the agent-half / user-half split that worked for Phase 13a/13b and Phase 15-release. The user clicks through the 4-port demo against the new desktop chrome (collapse animation in project-config Modal; tab-content fade; window icon in title bar; status badge typed migration; empty-tabs icon polish) + SPA polish (`RpcErrorToast` on send failures; Placeholder skeletons during ticket-list and comment-list initial fetches; Breadcrumb on ticket detail), captures 5 fresh desktop PNGs per `tests/baselines/desktop-README.md`, stamps Section H + F + M Human? cells in `docs/dev/e2e_checklist.md`, then `git tag v0.2.0` on `development` and pushes. Estimated ~1 session. Full next-session entry: `prompt.md` §3 — Candidate A (Phase 16b-release; this issue's closure path) OR Candidate B (Phase 18c Playwright suite, [#3](https://forge.ourworld.tf/lhumina_code/hero_assistance/issues/3); ~2–3 sessions; B.5 runs).
Author
Owner

Session 30 (2026-05-01) — Phase 16b-fix complete; v0.2.0 deferred

Commits on development (pushed):

  • 9a1fed1feat(_app,_ui_wasm)(session-30): Phase 16b-fix — × truncate + disable-on-N=1 guard + Link→button (4 sites) + Breadcrumb hand-roll
  • 378b291chore(sessions,docs)(session-30): record session-30 manifest + CLAUDE.md/prompt.md handoff + 4 fresh desktop PNGs + findings doc

What was tested (Rule-6 walkthrough)

User-driven walkthrough of _app desktop binary against the seeded 2-project demo (scripts/phase13_demo.sh). Six §3 walkthrough items were exercised:

# Item Verdict
1 W7 window icon (_NET_WM_ICON) ⚠ environmental — WM doesn't honor; dock derives from WM_CLASS + needs .desktop file install
2 Dark theme on first paint (CUSTOM_HEAD)
3 W4 Modal Collapse on ⚙ (Add subscription form)
4 W4 Tab fade (.app-tab-pane 150ms opacity)
5 W3 typed Badge (connecting…connected (2))
6 W3 EmptyTabs bi-collection icon ⚠ blocked by B5 (empty-launch render bug)

Bugs found — 7 desktop findings

Full record in runs/phase16b_findings.md.

# Title Status
B1 × Modal column clipped offscreen (long sock paths overflow ModalSize::Lg) FIXED in _app/src/project_config.rs (text-truncate + max-width: 14rem + title= tooltip)
B2 N=1→0 subscription removal deadlocks UI thread ⚠ GUARDED with disabled-on-N=1; spawn-deferral alone insufficient. Real fix deferred. Root cause: tokio drop ordering of Arc<Mutex<UnboundedReceiver>> (NativeEventsFeed) when last ProjectTab drops simultaneously with multiple use_future cancellations
B3 Ticket Link click escapes _app to system browser (Brave opens file:///) FIXED at four call sites: Link { to: Route::TicketDetail }button { onclick: nav.push(...) } + use_navigator(). Sites: tickets/list.rs::TicketCard + AnonymousCta + tickets/detail.rs::TicketDetail Anonymous Alert + enrollment.rs::DoneStep. Root cause: <a href="/tickets/N"> resolves to file:/// under the dioxus:// base; webkit2gtk navigation_handler doesn't trap it; webbrowser::open() fallback opens system browser
B4 Breadcrumb Home/Tickets links escape to system browser FIXED in _ui_wasm/src/components/tickets/detail.rs — replaced typed Breadcrumb + BreadcrumbItem { href } with hand-rolled nav[aria-label="breadcrumb"] > ol.breadcrumb > li.breadcrumb-item > button. L5 Playwright spec (breadcrumb.spec.ts) still passes (asserts on text + .breadcrumb-item count, not <a> shape)
B5 Empty-launch shows blank screen (right pane never paints EmptyTabs) ⚠ DEFERRED — unknown root cause; no panic, no error log. Repro: pkill hero_assistance_app; rm -f ~/.local/share/hero_assistance/subscriptions.toml; HERO_ASSISTANCE_SUBSCRIPTIONS= ./target/release/hero_assistance_app
B6 Save persists to TOML masking env-var on next launch DOCUMENTED — intended UX behaviour; precedence is env-var > toml per _app/src/config.rs::resolve
B7 Window icon (title bar + dock) shows generic gear ENVIRONMENTAL — WM doesn't honor _NET_WM_ICON; dock requires .desktop file install (post-v1 packaging concern); _app code path verified correct

Test posture

cargo test --no-fail-fast → 73 integration passing + 32 _ui_wasm lib + everywhere else; 3 L-03 inherited failures + 1 phase10 transient flake unchanged from s29 — zero regressions from today's source fixes. Kickstart's verify-block extended +16 (374 → 390); all assertions pass.

4 fresh desktop PNGs captured this session (desktop-{acme,detail,globex,globex-error}.png) reflecting today's source state including the 4 navigation fixes. desktop-empty.png retained from s23 pending B5 fix. Section M-11..M-14 dates bumped to 2026-05-01; M-10 keeps 2026-04-30 stamp. Section H Human? stamps: H-1/H-8/H-13 walked s23 + H-5/H-10/H-12 walked s30 = 6 ; H-15 = [skip: WM-ENV-NO-ICON-DISPLAY] per environmental finding.

v0.2.0 tag — DEFERRED

The polish surface materially shipped today (4 navigation bugs fixed, walkthrough Items 1-5 verified , Item 6 component code verified by code grep). However, B5 (empty-launch blank) and B2 (real fix) are unresolved, so the user opted to defer the v0.2.0 tag until both close in a clean walkthrough.

Architectural lesson recorded: the SPA path (_ui --dist over socat → port 8083-8086, browser-driven via Playwright/MCP) covers ~70% of UI surface — sign-in / ticket list / detail / breadcrumb / events / RPC — already automated by un-skipped L5 specs (s29). The _app-only outer shell (window chrome / multi-project tab strip / Manage subscriptions Modal / aggregator left panel / assist:// custom protocol / D-17 TOML persistence / theme on first paint) is irreducibly desktop and is the only surface that requires Rule-6 walkthrough. 5 of today's 7 bugs were _app-only webview quirks — ticket Link escape, Breadcrumb escape, N=1→0 deadlock, empty-launch blank, window icon environmental. Future Rule-6 sessions on _app reserved for the minimum desktop-chrome surface; everything else stays in the SPA / Playwright lane.

What's next — Phase 16b-release-fix → tag v0.2.0

Tracked here. Two _app-irreducible items remain, both deferred from this session:

  1. B5 empty-launch blank-screen fix. Add tracing::info! counters in _app/src/main.rs::with_custom_protocol to confirm assist:// CSS loads on empty-boot (rule out CSS-load); inspect display: grid; grid-template-columns: 360px 1fr under minimal right-pane content; check Dioxus 0.7 hook-stack edge cases under zero-yield for loop. ~30 min investigation + fix.
  2. B2 N=1→0 deadlock real fix. Drop the disabled: subscriptions().len() <= 1 guard. Refactor _app/src/app.rs::ProjectTab's per-tab use_future driver task to use an explicit shutdown signal (tokio::sync::Notify or watch::channel<bool>) instead of relying on Sender drop semantics + Mutex lock release during simultaneous component drop. ~1-2 hours.

Re-walk Items 1-6 in _app with both fixes landed (all six should be ), re-capture desktop-empty.png, then git tag -a v0.2.0 on development. ~1 session, B.5 skip.

Independent alternative if user prefers backend work first: L-04 + L-03 closures (users table UNIQUE(email) + display_name migration + 3 inherited test failures closed). B.5 RUNS (schema migration). ~1 session.

See prompt.md §3 Candidate A vs B for full work-item lists.

## Session 30 (2026-05-01) — Phase 16b-fix complete; v0.2.0 deferred **Commits on `development`** (pushed): - [`9a1fed1`](https://forge.ourworld.tf/lhumina_code/hero_assistance/commit/9a1fed1) — `feat(_app,_ui_wasm)(session-30): Phase 16b-fix — × truncate + disable-on-N=1 guard + Link→button (4 sites) + Breadcrumb hand-roll` - [`378b291`](https://forge.ourworld.tf/lhumina_code/hero_assistance/commit/378b291) — `chore(sessions,docs)(session-30): record session-30 manifest + CLAUDE.md/prompt.md handoff + 4 fresh desktop PNGs + findings doc` ### What was tested (Rule-6 walkthrough) User-driven walkthrough of `_app` desktop binary against the seeded 2-project demo (`scripts/phase13_demo.sh`). Six §3 walkthrough items were exercised: | # | Item | Verdict | |---|---|---| | 1 | W7 window icon (`_NET_WM_ICON`) | ⚠ environmental — WM doesn't honor; dock derives from `WM_CLASS` + needs `.desktop` file install | | 2 | Dark theme on first paint (`CUSTOM_HEAD`) | ✅ | | 3 | W4 Modal Collapse on ⚙ (`Add subscription` form) | ✅ | | 4 | W4 Tab fade (`.app-tab-pane` 150ms opacity) | ✅ | | 5 | W3 typed Badge (`connecting…` → `connected (2)`) | ✅ | | 6 | W3 EmptyTabs `bi-collection` icon | ⚠ blocked by B5 (empty-launch render bug) | ### Bugs found — 7 desktop findings Full record in [`runs/phase16b_findings.md`](https://forge.ourworld.tf/lhumina_code/hero_assistance/src/branch/development/runs/phase16b_findings.md). | # | Title | Status | |---|---|---| | B1 | × Modal column clipped offscreen (long sock paths overflow `ModalSize::Lg`) | ✅ FIXED in `_app/src/project_config.rs` (`text-truncate` + `max-width: 14rem` + `title=` tooltip) | | B2 | N=1→0 subscription removal deadlocks UI thread | ⚠ GUARDED with `disabled-on-N=1`; spawn-deferral alone insufficient. Real fix deferred. Root cause: tokio drop ordering of `Arc<Mutex<UnboundedReceiver>>` (NativeEventsFeed) when last `ProjectTab` drops simultaneously with multiple `use_future` cancellations | | B3 | Ticket Link click escapes `_app` to system browser (Brave opens `file:///`) | ✅ FIXED at four call sites: `Link { to: Route::TicketDetail }` → `button { onclick: nav.push(...) }` + `use_navigator()`. Sites: `tickets/list.rs::TicketCard` + `AnonymousCta` + `tickets/detail.rs::TicketDetail` Anonymous Alert + `enrollment.rs::DoneStep`. Root cause: `<a href="/tickets/N">` resolves to `file:///` under the `dioxus://` base; webkit2gtk navigation_handler doesn't trap it; `webbrowser::open()` fallback opens system browser | | B4 | Breadcrumb Home/Tickets links escape to system browser | ✅ FIXED in `_ui_wasm/src/components/tickets/detail.rs` — replaced typed `Breadcrumb` + `BreadcrumbItem { href }` with hand-rolled `nav[aria-label="breadcrumb"] > ol.breadcrumb > li.breadcrumb-item > button`. L5 Playwright spec (`breadcrumb.spec.ts`) still passes (asserts on text + `.breadcrumb-item` count, not `<a>` shape) | | B5 | Empty-launch shows blank screen (right pane never paints `EmptyTabs`) | ⚠ DEFERRED — unknown root cause; no panic, no error log. Repro: `pkill hero_assistance_app; rm -f ~/.local/share/hero_assistance/subscriptions.toml; HERO_ASSISTANCE_SUBSCRIPTIONS= ./target/release/hero_assistance_app` | | B6 | Save persists to TOML masking env-var on next launch | DOCUMENTED — intended UX behaviour; precedence is env-var > toml per `_app/src/config.rs::resolve` | | B7 | Window icon (title bar + dock) shows generic gear | ENVIRONMENTAL — WM doesn't honor `_NET_WM_ICON`; dock requires `.desktop` file install (post-v1 packaging concern); `_app` code path verified correct | ### Test posture `cargo test --no-fail-fast` → 73 integration passing + 32 `_ui_wasm` lib + everywhere else; **3 L-03 inherited failures + 1 phase10 transient flake unchanged from s29 — zero regressions** from today's source fixes. Kickstart's verify-block extended +16 (374 → 390); all assertions pass. **4 fresh desktop PNGs** captured this session (`desktop-{acme,detail,globex,globex-error}.png`) reflecting today's source state including the 4 navigation fixes. `desktop-empty.png` retained from s23 pending B5 fix. Section M-11..M-14 dates bumped to 2026-05-01; M-10 keeps 2026-04-30 stamp. Section H Human? stamps: H-1/H-8/H-13 walked s23 + H-5/H-10/H-12 walked s30 = 6 ✅; H-15 = `[skip: WM-ENV-NO-ICON-DISPLAY]` per environmental finding. ### v0.2.0 tag — DEFERRED The polish surface materially shipped today (4 navigation bugs fixed, walkthrough Items 1-5 verified ✅, Item 6 component code verified by code grep). However, B5 (empty-launch blank) and B2 (real fix) are unresolved, so the user opted to **defer the v0.2.0 tag** until both close in a clean walkthrough. **Architectural lesson recorded:** the SPA path (`_ui --dist` over socat → port 8083-8086, browser-driven via Playwright/MCP) covers ~70% of UI surface — sign-in / ticket list / detail / breadcrumb / events / RPC — already automated by un-skipped L5 specs (s29). The `_app`-only outer shell (window chrome / multi-project tab strip / `Manage subscriptions` Modal / aggregator left panel / `assist://` custom protocol / D-17 TOML persistence / theme on first paint) is irreducibly desktop and is the only surface that requires Rule-6 walkthrough. **5 of today's 7 bugs were `_app`-only webview quirks** — ticket Link escape, Breadcrumb escape, N=1→0 deadlock, empty-launch blank, window icon environmental. Future Rule-6 sessions on `_app` reserved for the minimum desktop-chrome surface; everything else stays in the SPA / Playwright lane. ### What's next — Phase 16b-release-fix → tag v0.2.0 Tracked here. Two `_app`-irreducible items remain, both deferred from this session: 1. **B5 empty-launch blank-screen fix.** Add `tracing::info!` counters in `_app/src/main.rs::with_custom_protocol` to confirm assist:// CSS loads on empty-boot (rule out CSS-load); inspect `display: grid; grid-template-columns: 360px 1fr` under minimal right-pane content; check Dioxus 0.7 hook-stack edge cases under zero-yield `for` loop. ~30 min investigation + fix. 2. **B2 N=1→0 deadlock real fix.** Drop the `disabled: subscriptions().len() <= 1` guard. Refactor `_app/src/app.rs::ProjectTab`'s per-tab `use_future` driver task to use an explicit shutdown signal (`tokio::sync::Notify` or `watch::channel<bool>`) instead of relying on Sender drop semantics + Mutex<UnboundedReceiver> lock release during simultaneous component drop. ~1-2 hours. Re-walk Items 1-6 in `_app` with both fixes landed (all six should be ✅), re-capture `desktop-empty.png`, then `git tag -a v0.2.0` on `development`. ~1 session, B.5 skip. Independent alternative if user prefers backend work first: **L-04 + L-03 closures** (`users` table `UNIQUE(email)` + `display_name` migration + 3 inherited test failures closed). B.5 RUNS (schema migration). ~1 session. See [`prompt.md` §3](https://forge.ourworld.tf/lhumina_code/hero_assistance/src/branch/development/prompt.md) Candidate A vs B for full work-item lists.
Author
Owner

Closed by v0.2.0 (12601f4; session 32, Phase 16b-fix-redux).

Root cause

The s30 spawn-yield + s31 watch-channel + first half of s32 driver-out-of-ProjectTab attempts all targeted async lifecycle in the per-tab driver task. The actual B2 wedge was a synchronous render-storm in the selected_tab auto-pick use_effect, masked since 15d (session 22) by the s30/s31 disable-on-N=1 UI guard.

Diagnosis came from /proc/<pid>/status showing state R (Running), not S/D futex_wait — telling us this was a CPU-bound spin, not a deadlock. The auto-pick effect read selected_tab() (subscribes effect to signal) AND wrote selected_tab.set(...); on N=1→0 with current=Some(stale)/next=None, the set-and-self-notify cycle ran at ~60fps until the WM tripped its "not responding" detector. Dioxus 0.7 Signal::set notifies unconditionally (no PartialEq short-circuit).

Fix (2 lines): .peek() for the read + skip-no-op-write equality check.

What landed

  • Driver-out-of-ProjectTab refactor stays — _app::App owns per-tab driver lifecycle via Drivers map (Rc<RefCell<HashMap<ProjectId, DriverEntry>>>) + feeds/tab_status Signal maps + diffing use_effect (synchronous-only, off-UI cancellation via tokio_util::sync::CancellationToken). ProjectTab is purely presentational.
  • Failed(msg) UX path — bogus rpc.sock paths now show a red Connection-failed alert with a "Remove and re-add via the gear menu to retry" recovery hint instead of an indefinite spinner.
  • Status badge reads live subs count; shows "no projects" on N=0 (boot-snapshot aggregator's stale count was misleading post-mutation).
  • Dropped the s30 spawn-yield workaround + s31 disabled: is_only UI guard from project_config.rs.

Walkthrough verdicts (all 5 acceptance scenarios green)

Scenario Result
N=2 → N=1 (remove first sub) Driver shutdown logged at ~200µs (removed → cancelled → exited)
N=1 → N=0 (the deadlock case) Right pane flips to EmptyTabs CTA. No "not responding" dialog.
Add-back from empty Tab mounts with Connecting spinner → flips to embedded App
Stress add/remove cycles Clean transitions to/from EmptyTabs
Failed-connection UX Red alert with recovery hint; no freeze

Test posture

L1: 209 native passing (+5 s32 unit tests for diff set-arithmetic + ReadOnlyRpc Arc-pointer-identity equality) / 32 _ui_wasm lib unchanged. L2/L3/L4/L5/L6/L7: unchanged from s31. 3 L-03 inherited + 1 phase10 transient flake unchanged. Build (cargo build --release) + wasm32 (cargo check) green. Kickstart 431 verify assertions all pass (423 → 431).

Lessons recorded (runs/phase16b_findings.md)

  1. When the WM dialog says "not responding," check /proc/<pid>/status first. State R = spin (CPU-bound loop); state S/D with futex_wait on the main thread = deadlock. Right diagnostic narrows the search by an order of magnitude.
  2. Dioxus 0.7 Signal::set notifies subscribers unconditionally. Effects that read AND write the same Signal must use .peek() for the read OR guard the write with an equality check (or both).
  3. UX guards that mask reachability of latent bugs should be tagged so removal triggers root-cause investigation. The s30/s31 disable-on-N=1 guard concealed this for ~10 sessions.
**Closed by v0.2.0** ([`12601f4`](https://forge.ourworld.tf/lhumina_code/hero_assistance/commit/12601f4e447dfbbd89eabb1e386b4bf315b62948); session 32, Phase 16b-fix-redux). ## Root cause The s30 spawn-yield + s31 watch-channel + first half of s32 driver-out-of-`ProjectTab` attempts all targeted async lifecycle in the per-tab driver task. The actual B2 wedge was a **synchronous render-storm** in the `selected_tab` auto-pick `use_effect`, masked since 15d (session 22) by the s30/s31 disable-on-N=1 UI guard. **Diagnosis** came from `/proc/<pid>/status` showing state `R (Running)`, not `S/D` futex_wait — telling us this was a CPU-bound spin, not a deadlock. The auto-pick effect read `selected_tab()` (subscribes effect to signal) AND wrote `selected_tab.set(...)`; on N=1→0 with current=Some(stale)/next=None, the set-and-self-notify cycle ran at ~60fps until the WM tripped its "not responding" detector. Dioxus 0.7 `Signal::set` notifies unconditionally (no PartialEq short-circuit). **Fix** (2 lines): `.peek()` for the read + skip-no-op-write equality check. ## What landed - **Driver-out-of-`ProjectTab` refactor** stays — `_app::App` owns per-tab driver lifecycle via `Drivers` map (`Rc<RefCell<HashMap<ProjectId, DriverEntry>>>`) + `feeds`/`tab_status` Signal maps + diffing `use_effect` (synchronous-only, off-UI cancellation via `tokio_util::sync::CancellationToken`). `ProjectTab` is purely presentational. - **Failed(msg) UX path** — bogus rpc.sock paths now show a red Connection-failed alert with a "Remove and re-add via the gear menu to retry" recovery hint instead of an indefinite spinner. - **Status badge** reads live subs count; shows "no projects" on N=0 (boot-snapshot aggregator's stale count was misleading post-mutation). - Dropped the s30 `spawn-yield` workaround + s31 `disabled: is_only` UI guard from `project_config.rs`. ## Walkthrough verdicts (all 5 acceptance scenarios green) | Scenario | Result | |---|---| | N=2 → N=1 (remove first sub) | ✅ Driver shutdown logged at ~200µs (removed → cancelled → exited) | | N=1 → N=0 (the deadlock case) | ✅ Right pane flips to EmptyTabs CTA. **No "not responding" dialog.** | | Add-back from empty | ✅ Tab mounts with Connecting spinner → flips to embedded App | | Stress add/remove cycles | ✅ Clean transitions to/from EmptyTabs | | Failed-connection UX | ✅ Red alert with recovery hint; no freeze | ## Test posture L1: 209 native passing (+5 s32 unit tests for diff set-arithmetic + ReadOnlyRpc Arc-pointer-identity equality) / 32 `_ui_wasm` lib unchanged. L2/L3/L4/L5/L6/L7: unchanged from s31. 3 L-03 inherited + 1 phase10 transient flake unchanged. Build (`cargo build --release`) + wasm32 (`cargo check`) green. **Kickstart 431 verify assertions all pass** (423 → 431). ## Lessons recorded ([`runs/phase16b_findings.md`](https://forge.ourworld.tf/lhumina_code/hero_assistance/src/branch/development/runs/phase16b_findings.md)) 1. When the WM dialog says "not responding," check `/proc/<pid>/status` first. State `R` = spin (CPU-bound loop); state `S/D` with futex_wait on the main thread = deadlock. Right diagnostic narrows the search by an order of magnitude. 2. Dioxus 0.7 `Signal::set` notifies subscribers unconditionally. Effects that read AND write the same Signal must use `.peek()` for the read OR guard the write with an equality check (or both). 3. UX guards that mask reachability of latent bugs should be tagged so removal triggers root-cause investigation. The s30/s31 disable-on-N=1 guard concealed this for ~10 sessions.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_assistance#2
No description provided.