Skip to content

feat(ag-ui): App-mode promo hero in sidebar mode#737

Open
blove wants to merge 7 commits into
mainfrom
blove/ag-ui-app-mode-promo
Open

feat(ag-ui): App-mode promo hero in sidebar mode#737
blove wants to merge 7 commits into
mainfrom
blove/ag-ui-app-mode-promo

Conversation

@blove

@blove blove commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

What

Replaces the placeholder launcher hint in sidebar mode (App mode off) with a preview-led marketing hero that sells the App-mode map cockpit and the Threadplane primitives behind it, with a CTA that enables App mode.

  • New isolated AppModePromoComponent (modes/app-mode-promo.component.ts): hasMapsKey signal input + enable output, OnPush, no shell coupling.
  • A centered poster card: static Paris map image backdrop (object-fit: cover, dark fallback when absent) + an always-dark caption with a "Built with Threadplane" eyebrow, headline, four capability pills — Client tools, Generative UI, Human-in-the-loop, Shared state — and the primary CTA.
  • Wired into SidebarMode under the existing @if (shell.appMode() !== 'on') gate: [hasMapsKey]="shell.hasMapsKey", (enable)="shell.onAppModeChange('on')".
  • No-key fallback: CTA disabled + "Set GOOGLE_MAPS_API_KEY to enable" note (mirrors the toolbar toggle).
  • a11y/motion: real <button>, focus-visible ring, aria-hidden icons, prefers-reduced-motion gate, (error) guard hides a missing image to the dark fallback.

Spec: docs/superpowers/specs/2026-06-25-ag-ui-app-mode-promo-design.md
Plan: docs/superpowers/plans/2026-06-25-ag-ui-app-mode-promo.md

Verification

  • Unit (vitest): 45 pass, incl. 3 new (renders headline + 4 pills + enabled CTA; CTA emits enable; no-key disables CTA + shows note + title).
  • e2e (Playwright): full ag-ui suite 24/24 incl. new app-mode-promo.spec.ts (promo visible in sidebar mode).
  • Live click-through (real Maps key): sidebar → enabled CTA → /sidebar?appmode=on cockpit (map + overlay), promo gone.
  • Lint clean.

Follow-up (not blocking)

  • Add the static map screenshot at examples/ag-ui/angular/public/app-mode-preview.webp (drop the raw PNG; I'll resize/convert via sips). Until then the component shows its dark fallback — layout intact, no broken-image badge.

🤖 Generated with Claude Code

blove and others added 7 commits June 24, 2026 22:44
Marketing surface for sidebar mode (App mode off): a preview-led poster
with a static Paris map backdrop, Threadplane capability pills (client
tools, generative UI, human-in-the-loop, shared state), and a CTA that
enables App mode. Replaces the placeholder launcher hint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5 tasks: TDD component (hasMapsKey input / enable output), wire into
SidebarMode under the existing gate, e2e (key-independent), static map
image with dark fallback + sips processing, verify + ship.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Final-review nits: add an (error) handler so a missing/404 map image hides
cleanly to the dark fallback (no broken-image badge on the demo surface),
and remove the color/font-size left on .sidebar-mode__background by the
deleted launcher hint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview, Comment Jun 25, 2026 5:56am

Request Review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 2m 0s —— View job


PR Review: feat(ag-ui): App-mode promo hero in sidebar mode

  • Gather context and read CLAUDE.md
  • Read changed files and diff
  • Review component implementation
  • Review tests
  • Post findings

Summary

Clean, well-scoped addition. The component is properly isolated (no shell coupling via DI), OnPush with signal inputs/outputs, a11y-aware (real <button>, focus-visible ring, aria-hidden icons), and the reduced-motion gate is correct. No correctness bugs, no security issues, no public API regressions.

Two minor observations (inline comments posted):

  1. (error) direct DOM mutation (app-mode-promo.component.ts:25) — The approach is fine; documented the one edge-case invariant (re-render after @if toggle refires the image request). No action needed.

  2. Missing symmetrical assertion in the first unit test (app-mode-promo.component.spec.ts:21) — The test verifies cta.disabled === false when the key is present, but doesn't assert the title attribute is absent. A one-liner expect(cta!.hasAttribute('title')).toBe(false) would close the gap. Suggestion included.

Everything else looks solid:

  • hasMapsKey is a plain boolean on the shell (environment-derived, immutable at runtime), bound correctly to the input<boolean>(false) signal input — no change-detection concern.
  • The [attr.title]="hasMapsKey() ? null : '...' pattern correctly removes the attribute via Angular's null-binding behaviour.
  • output<void>() + subscribe() in tests is valid Angular 17+ OutputRef API.
  • ::ng-deep override in sidebar-mode.component.ts is pre-existing and not introduced here.
  • e2e covers the promo being visible in sidebar mode; unit tests cover enabled CTA, emit, and disabled + note states.

Result: LGTM with one minor test suggestion. The follow-up for the static map asset (public/app-mode-preview.webp) is clearly called out in the PR and the dark fallback keeps the layout intact in the meantime.

src="/app-mode-preview.webp"
alt="Preview of the App-mode map cockpit"
loading="lazy"
(error)="promoImg.style.display = 'none'"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The direct DOM mutation in the error handler works and avoids a unnecessary component state property for this purely-visual side effect, so this is fine. One thing to be aware of: if the component is destroyed and re-created (e.g. after the @if gate toggles), the style.display = 'none' is gone and the broken image request fires again. Because the image URL is a static asset that either exists or doesn't, this is unlikely to be observed in practice, but if the asset lands later the behavior self-corrects on re-render. No action needed — just documenting the invariant.

expect(el.querySelectorAll('.promo__pill').length).toBe(4);
const cta = el.querySelector('.promo__cta') as HTMLButtonElement | null;
expect(cta).toBeTruthy();
expect(cta!.disabled).toBe(false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Consider adding a companion assertion that the title attribute is absent (or empty) when the key is present — the current test only covers the false branch. A one-liner suffices:

Suggested change
expect(cta!.disabled).toBe(false);
expect(cta!.disabled).toBe(false);
expect(cta!.hasAttribute('title')).toBe(false);

Not blocking, but it closes the symmetry gap with the third test case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant