Skip to content

STU-445: landing header, hero and composer fixes + stu385 CI race - #105

Merged
sgardoll merged 6 commits into
milestone/stu-implement-updated-ui-uxfrom
sgardoll/stu445-landing-chrome
Sep 28, 2026
Merged

sgardoll merged 6 commits into
milestone/stu-implement-updated-ui-uxfrom
sgardoll/stu445-landing-chrome

Conversation

@sgardoll

Copy link
Copy Markdown
Owner

STU-445 items addressed (owner's wording quoted)

Owner wording Change
"Tutorial → remove button for now" Removed the header's Tutorial entry (#wt-reopen). The walkthrough still auto-opens for fresh users; there is no user-facing reopen entry for now (the returning-user spec now reopens through the app's own openWalkthroughModal()).
"Header: Should just be the icon in black on the left (no box)" The brand is the paired-C ribbon drawn in --fg (the mock's brand-logomark geometry); the rounded-rect box and the FlutterFlow / Custom Code Connect wordmark are gone.
"Header: Remove Home Account Plans Tutorial [nav links]" The whole .topnav nav is gone. Account stays reachable via #topbar-avatar, Home via the logo, Plans via its /#plans deep link and the account surface's "Change plan" action.
"Header: remove background" .topbar no longer paints --bg or the mobile border-bottom; it keeps position: fixed so every existing --topbar-h layout offset is unchanged.
"Header: clicking logo should take the user home" .brand still links #home; new coverage proves Account → logo → Home, and avatar → Account.
"Home: No animation showing orange cursor and words being typed" The hero now types the shipped example prompt in behind the blinking orange caret (is-demo-typing; the .ghost .typed::after caret was already in the stylesheet). Starts on Home load, cancels on any real interaction, skipped under reduced motion and on non-home deep links.
"Home: onClick into input field a light blue outline box shows that is offset with the main orange outline" Root cause: the page-wide textarea:focus { box-shadow: 0 0 0 4px rgba(59,130,246,.1), … } rule painted a blue ring on #pipeline-input. .composer .field overrode its outline but never its box-shadow. The field now sets box-shadow: none, leaving the composer's own orange border + :focus-within ring.

stu385 CI race — diagnosis and fix (the red ci on PR #100)

Symptom on CI: e2e/stu385-composer-settings.spec.js:175 received [] for generatorModels in the initial run and both retries (Ubuntu, node 22.15.1, CI=1, 1 worker), while passing on macOS.

Root cause: a test-harness race, not an app race. The test waits for #generation-stage to be visible and then reads the captured array synchronously. runThinkingPipeline() reveals the stage in showPipelineProgress() before the architect request is issued; the generator request only leaves after the architect round-trip completes. On a fast local machine the whole mocked pipeline finishes before Playwright's visibility poll resolves (probe timings: click at 687 ms, architect 718 ms, generator 734 ms, stage observed visible at 748 ms). On slower CI the poll resolves while the architect step is still in flight, so the read sees []. The app's selection path is not at fault: the dialog's change handler forwards to the canonical #code-generator-model synchronously, and the request carries exactly what the selector holds.

Reproduction (the break): adding a 250 ms delay to the architect route in the real spec made it fail 9/9 locally with Received: Array [], exactly matching CI:
CI=1 CCC_TEST_PORT=3105 npx playwright test e2e/stu385-composer-settings.spec.js --grep "a model chosen in the dialog" --repeat-each=3 → 9 failed.

Fix: observe the request itself with expect.poll(() => generatorModels).toEqual(["anthropic/claude-opus-5"]). No timeout increase, no weakened assertion.

  • With the 250 ms architect delay still in place: 5/5 passed.
  • Restored, required reliability run: CI=1 CCC_TEST_PORT=3105 npx playwright test e2e/stu385-composer-settings.spec.js --repeat-each=10 → 80 passed (10 consecutive runs of all 8 specs).

Fail → pass evidence per acceptance criterion

Each new assertion was run against the pre-change app (temporarily restored index.html + src/composerAdapter.js), watched to FAIL, then restored and watched to PASS.

Criterion Before (FAIL) After (PASS)
Header icon-only, no box/wordmark/nav, no background npx playwright test e2e/stu375-hero-navigation.spec.js --grep "header shows only" → 3 failed same → 1 passed
Mobile Home/Account reachability (logo 44px target) --grep "mobile topbar keeps" → 3 failed same → 1 passed
Hero typing: visible text changes over time npx playwright test e2e/stu376-composer.spec.js --grep "hero demo types" → 3 failed same → 1 passed
Composer focus: only the orange outline (computed styles) --grep "clicking the prompt input shows only" → 3 failed same → 1 passed
stu385 model propagation slow-architect break → 9 failed with [] poll fix → 5 passed with the same break; 80 passed without it

The typing test samples .ghost .typed from navigation commit and asserts every sample is a strictly longer prefix of the shipped prompt (text CHANGES over time), ending on the full prompt with the caret class retired.

Full CI sequence on the pushed head

Step Result
CI=1 npm test 354 pass / 0 fail (baseline 354)
CI=1 npm run build pass
CI=1 CCC_TEST_PORT=3105 npm run test:ui 210 passed (baseline 206 + 4 new)
CI=1 npm run test:accessibility 7 shells passed
CI=1 npm run test:deploy-guard 9/9 passed
CI=1 npm run test:proxies 30/30 passed
CI=1 python3 scripts/scan_bundle_for_leaks.py clean

Judgement calls (flagging explicitly)

  • Specs outside the enumerated list: the brief listed the a.nav-link[…] clicks to update, but removing the nav links also broke e2e/stu375-hero-navigation.spec.js (its mobile/navigation/focus tests clicked .topnav a[data-view=…]), e2e/stu389-hero-mark-field.spec.js:153,173 (a[data-view=…]), and e2e/stu391-tutorial-onboarding.spec.js (#wt-reopen). They were updated to the surviving controls; the rest of their assertions are untouched. Without this, the full suite cannot be green.
  • New tests: 4 tests were added (header + logo/avatar navigation in stu375; typing demo + computed focus styles in stu376) because the acceptance criteria require fail→pass evidence and no existing test covered those behaviours.
  • src/composerAdapter.js was edited: the brief named "hero typing demo" under app.js ownership, but the composer's state machine (and therefore send gating for a mid-demo submit) lives in this module. The edit is confined to the composer's own typing/suggestion logic.
  • switchView() cleanup: removed the now-orphaned .nav-link[data-view] aria-current loop.
  • stu391 helper race (pre-existing): an early-opened walkthrough could be closed by the startup identity pass (updateUsageDisplay → showPaywallExhausted closes the open modal). The old click helper raced it too (the baseline CI log shows one of these tests failing its first attempt at 30.2 s). The helper now waits for the app's own usage signal (ccc_usage written) before opening.

Not implemented / not verified

  • No user-facing Tutorial reopen entry ("remove button for now").
  • Header stays position: fixed (the mock uses absolute); only the background/border was removed, so the --topbar-h offsets are unchanged.
  • The avatar stays 32 px (the mock's size); not in scope.
  • The hero demo plays on every Home load; if it should run once per session, that is a one-line gate.

@sgardoll
sgardoll marked this pull request as ready for review September 28, 2026 08:16
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits September 28, 2026 15:39
…ing-chrome

Both sides replaced the racy synchronous read of generatorModels with a
polled expectation; keep the landed version's poll-with-timeout.

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
…ing-chrome

reopenWalkthrough keeps the modal-function reopen (STU-445 removed the
header entry) but drops the ccc_usage wait: the base's updateUsageDisplay
now defers the paywall while a dialog is open, so a passive exhausted
resolution no longer closes an open tour.

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits September 28, 2026 17:12
A mid-demo click/keypress canceled the typing timer but left the frozen
prefix in the field, so a Generate click or Enter could submit a
half-typed example. Takeover now lands the demo's own end state — the
complete shipped prompt with the caret retired — via finishHeroDemo.

Two takeover shapes skip the restore instead: an interaction arriving
with text already selected (select-all, assistive insert) is an edit in
flight — overwriting the field would destroy the selection the incoming
replacement needs — and a takeover during the hold period has nothing
typed to complete. Both freeze the demo on the prefix and mark it
demo-owned: send stays disabled and Enter stays inert until a real
input event hands the text to the user.

Also fixes the reduced-motion listener lifecycle: a pagehide dispose
made bfcache restores lose the listener, so a demo replayed after
back-forward navigation ignored a later flip. The listener now lives
for the document's lifetime (dispose reserved for genuine teardown),
and a repeat initComposer on the same DOM replaces the dead
registration via a WeakMap instead of stacking a second listener.

Adds an e2e covering a mid-demo click followed by an immediate submit,
and demo-completion waits in smoke/stu376 where prompts were being
polled mid-type.

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
Takeover restore landed the full shippedPrompt in the field, but a
keystroke, Backspace, paste, drop or IME commit that followed then
joined the example — the browser inserts into whatever the field
holds, and the input handler treated the combination as user text.

Text the demo supplied while interrupted is now tracked as
demo-owned (heroDemoOwned) through the frozen and restored states.
A beforeinput listener — the single funnel every real edit passes
through — clears it before the native mutation lands, so typing
mid-demo produces a prompt of the user's own and Backspace removes
demo text rather than deleting inside the example. A demo that
finishes untouched hands the shipped text back as the field's own
content, so ordinary editing resumes.

New e2e coverage: mid-demo typing yields only the typed text, and
Backspace during the demo leaves an empty field.

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
…ui-ux' into pr105

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>

# Conflicts:
#	e2e/stu385-composer-settings.spec.js
#	e2e/stu391-tutorial-onboarding.spec.js
#	src/composerAdapter.js
devin-ai-integration[bot]

This comment was marked as resolved.

- cancelTyping() lands each running demo's end state instead of abandoning
  a submittable prefix (view switches, tab hides).
- A pointerdown takeover releases demo ownership after the restore, so the
  first Backspace trims the complete prompt; focus/keydown restores stay
  demo-owned so typing mid-demo still writes a fresh prompt.
- e2e: click+Backspace trims one char; switchView during typing and during
  the hold both settle the shipped prompt and Generate submits it whole.

Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com>
@sgardoll
sgardoll merged commit 34d1c0f into milestone/stu-implement-updated-ui-ux Sep 28, 2026
1 of 2 checks passed
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