Repository navigation
Remove composer autocomplete; hold the demo caret solid while keys land - #126
Merged
Merged
Conversation
…keys land Autocomplete is out until it is re-scoped in its own milestone. Typing no longer produces a ghost completion: the 13 local patterns, the suggestion session, the debounce, Tab-to-accept, Escape-to-dismiss, the Tab hint button and the polite live region are all removed. Tab and Escape keep their native behaviour. The ghost layer stays because it positions the demo caret, and the input handler now mirrors into it directly (the suggestion helpers used to do that). The orange demo caret now behaves like a real text cursor: is-demo-keying holds it solid on every character the hero demo types or erases, and on every character a chip types. It blinks only while the field is idle: the hold before typing and the rest on the full example. Before, it blinked through the backspace, so it was invisible in about half the frames. Tests: suggestion unit and e2e specs replaced by "typing makes no suggestion" and "Tab moves focus and never changes the prompt". The hero spec now asserts the caret's computed state on every sampled frame (blinking at the hold and the rest; animation none and opacity 1 on each typed or erased character). New spec: a chip fills behind a solid caret.
Applies the animation-skill timing model (GSAP timelines, Remotion's typewriter): every frame is derived from elapsed time, built by string slicing, rather than advanced one step per timer callback. Chained timers stretch the animation when the main thread is busy. That is what made the chip-fill spec time out under parallel test load, and it would drag the demo on a slow phone. Now a late tick lands everything already due: frames drop, the timing holds. - heroDemoFrame(elapsed, length) is the demo as a pure timeline: hold, then type at 73ms/char, rest 1.6s, then erase at 35ms/char. For any moment it returns the characters showing, whether the caret is keying (solid) or idle (blinking), whether the demo is done, and the ms until the next change. The runtime renders the frame due now and sleeps until the next one. - The chip fill reads its character count off the clock the same way. Timing is unchanged when nothing runs late. The timeline is now unit tested without a browser: exact phase boundaries, a late tick catching up, and a 1ms sweep showing the count rises and falls by exactly one character at a time.
Under parallel local runs these failed intermittently. Baseline on #126's tree, before this change: 5/20 in one --repeat-each batch. - stu445 morph: a fixed 1250ms sleep read the reveal before a slow morph had handed off. It now polls for the reveal, and waits for the 1400ms motion sample to exist before reading it. Same expected values. - stu389 field: counted frames over 500ms against a >5 bar, and read 5 at about 10fps. It now counts over 1500ms with the same bar. - stu390 logo loop: two re-entry checks compared counts from separate windows (framesB < framesA * c). The file's own comment says this trips when a slow runner stalls the baseline. They now use the file's existing framesPerDisplayFrame same-window ratio (< 1.6x), which still catches a doubled loop. After: 60/60 and 135/135 across repeat batches, and 3 consecutive clean full runs (265/265).
sgardoll
added a commit
that referenced
this pull request
Oct 12, 2026
* feat(composer): remove autocomplete; hold the demo caret solid while keys land Autocomplete is out until it is re-scoped in its own milestone. Typing no longer produces a ghost completion: the 13 local patterns, the suggestion session, the debounce, Tab-to-accept, Escape-to-dismiss, the Tab hint button and the polite live region are all removed. Tab and Escape keep their native behaviour. The ghost layer stays because it positions the demo caret, and the input handler now mirrors into it directly (the suggestion helpers used to do that). The orange demo caret now behaves like a real text cursor: is-demo-keying holds it solid on every character the hero demo types or erases, and on every character a chip types. It blinks only while the field is idle: the hold before typing and the rest on the full example. Before, it blinked through the backspace, so it was invisible in about half the frames. Tests: suggestion unit and e2e specs replaced by "typing makes no suggestion" and "Tab moves focus and never changes the prompt". The hero spec now asserts the caret's computed state on every sampled frame (blinking at the hold and the rest; animation none and opacity 1 on each typed or erased character). New spec: a chip fills behind a solid caret. * fix(paywall): an out-of-runs user writes their prompt before the paywall The "Free generations used" screen used to replace the composer the moment the page loaded for anyone already at their limit: updateUsageDisplay() surfaced it on every usage update (startup identity, subscription refresh), so the user never got to write a prompt. Now the paywall only answers a generation attempt. updateUsageDisplay() only clears it once the limit no longer applies (upgrade, sign-in, new month); canRunPipeline() shows it when a prompt is submitted at the limit, as before. That submitted prompt now fills the "Your prompt" recap beside the upgrade path, and becomes pipelineState.submittedPrompt, so Edit prompt returns to it instead of an earlier run's prompt. hasActiveModal() only existed to defer the auto-surfaced paywall while a dialog was open, so it is removed. Tests: stu391's startup spec now asserts that the exhausted startup leaves the user on the composer (it previously asserted the paywall appeared on reload), and that a run attempt shows the prompt in the recap. New stu382 spec covers the full flow: land on the composer, the paywall appears on submit with the prompt and pricing open, no pipeline request is sent, and Edit prompt returns to the prompt. * fix(paywall): keep Home on the composer after editing or a quota correction Two Devin Review findings on #127: - A usage update that dropped the count below the limit while the paywall was up hid the paywall but left the generation stage owning Home, so the user faced an empty panel instead of the composer. updateUsageDisplay() now also restores the composer when the paywall it hides was the live panel — covering both the count correction and the unresolved- subscription paths, and leaving progress/results panels alone. - Edit prompt hid the stage but left the paywall live inside it and the main-stage latch set, so a Plans trip and back reopened the paywall with no new submit. Editing now dismisses the paywall too, and switchView only restores the stage while a panel inside it is still live (progress, results, paywall or ready state) via generationStageHasLivePanel(). Two new stu382 specs cover both: a held identity response correcting the count mid-paywall hands the composer back with the draft, and a Plans round-trip after Edit prompt keeps Home on the composer. Both fail on the pre-change app.js. Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com> * build: rebuild dist Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com> * refactor(composer): drive the hero demo and chip fill from the clock Applies the animation-skill timing model (GSAP timelines, Remotion's typewriter): every frame is derived from elapsed time, built by string slicing, rather than advanced one step per timer callback. Chained timers stretch the animation when the main thread is busy. That is what made the chip-fill spec time out under parallel test load, and it would drag the demo on a slow phone. Now a late tick lands everything already due: frames drop, the timing holds. - heroDemoFrame(elapsed, length) is the demo as a pure timeline: hold, then type at 73ms/char, rest 1.6s, then erase at 35ms/char. For any moment it returns the characters showing, whether the caret is keying (solid) or idle (blinking), whether the demo is done, and the ms until the next change. The runtime renders the frame due now and sleeps until the next one. - The chip fill reads its character count off the clock the same way. Timing is unchanged when nothing runs late. The timeline is now unit tested without a browser: exact phase boundaries, a late tick catching up, and a 1ms sweep showing the count rises and falls by exactly one character at a time. * test: stop three animation specs racing the wall clock under load Under parallel local runs these failed intermittently. Baseline on #126's tree, before this change: 5/20 in one --repeat-each batch. - stu445 morph: a fixed 1250ms sleep read the reveal before a slow morph had handed off. It now polls for the reveal, and waits for the 1400ms motion sample to exist before reading it. Same expected values. - stu389 field: counted frames over 500ms against a >5 bar, and read 5 at about 10fps. It now counts over 1500ms with the same bar. - stu390 logo loop: two re-entry checks compared counts from separate windows (framesB < framesA * c). The file's own comment says this trips when a slow runner stalls the baseline. They now use the file's existing framesPerDisplayFrame same-window ratio (< 1.6x), which still catches a doubled loop. After: 60/60 and 135/135 across repeat batches, and 3 consecutive clean full runs (265/265). * fix(paywall): show only the screen on submit; pricing waits for View Plans Submitting a prompt at the limit used to open the pricing dialog on top of the "Free generations used" screen. Now it shows only the screen, and its View Plans button opens pricing when the user chooses. The openModal option had no other caller, so it is removed. Specs: stu382 asserts pricing stays closed on submit and opens from View Plans. stu391 asserts the run attempt leaves pricing closed. Mutation-checked: re-opening pricing on submit fails both. * fix(paywall): require the paywall element to exist for the visible check Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com> * build: rebuild dist Co-Authored-By: Stuart Gardoll <sgardoll@gmail.com> * test: stop the animation specs measuring the machine instead of the loop The three flaky specs asserted absolute frame counts in fixed wall-clock windows. A count is frames per second, so those floors measured how busy the runner was, not whether the animation worked: at 12 workers they failed 5 times per 128 runs with the loops running perfectly. Measured first. A running logo loop paints exactly one frame per display frame (3-11 frames under load, always equal); a stopped one paints none while display frames run 3-36. So the honest claim is expressed against display frames from the same window, in one shared helper: - stu390: replace every `frames > 5` floor with expectRunning(), which asserts the loop painted (logo > 0), the window contained real display frames, and painted at most once per display frame. steadyFramesIn and its fixed 250ms warm-up go with it. 256/256 at 12 workers, from 5 failures per 128. - stu389: the field is the only rAF driver on that page (framesOff is asserted to be exactly 0), so the count is the field's own. The >5 floor only added "the runner does 4fps"; rendering at all, then stopping to 0, is the claim. - stu445: six geometry tests run in parallel and each drives a whole gated pipeline, ~5.3s of authored-pacing sleeps plus ten geometry reads, so they exceeded the 30s default on the sleeps rather than on a mismatch. Give them a 60s budget. Also rebuilt the morph spec to record transient state from inside the page instead of racing the 1120ms morph from outside. open() now captures the ghost, the hidden destination and the reveal as they happen, and the handoff is recorded the frame the ghost leaves rather than at a fixed 1400ms. Checked from outside, a loaded machine finishes the morph before the check runs. Verified by mutation: deleting the data-morphing CSS rule fails the "destination hidden during the morph" assertion, and removing both clears of data-morphing fails the handoff assertion. Restoring the never-painting loop mutation fails expectRunning. Full suite 268/268 twice, 413 unit. --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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.
Follow-up to #125. This was pushed straight to
mainby mistake asb6ab54b, reverted in9c0dd93, and re-lands here unchanged for review. The branch tree matchesb6ab54bexactly. Nothing was deployed: the site only ships viascripts/deploy_ftp.py.1. Autocomplete removed (re-scoped in an upcoming milestone)
Typing no longer produces a ghost completion. Removed:
#suggest-statuslive region and the textarea'saria-describedbypointing at it.ghost .suggestspan and its CSSThe ghost layer itself stays, because it positions the demo caret. The input handler now mirrors typed text into it directly; the deleted suggestion helpers used to do that. A spec caught this regression during the change.
2. The demo caret now behaves like a real text cursor
Before, the caret blinked on a 1.05s cycle while text was typed or erased, so it was invisible in about half the frames of the backspace. Now:
is-demo-keyingclass holds the caret solid on every character the hero demo types or erases, and on every character a chip types.Frame strip from a real Chromium recording (5fps): the caret is visible on every typed and erased frame, blinks only during the 1.6s rest on
…gradient stroke, then the demo ends on the grey hint.Tests
npm test: 409/409 pass. The 14 suggestion unit tests went with the code; thecanSubmittest remains.npx playwright test: 265 passed, 0 failed. Net count: 8 suggestion specs removed and 5 added.caret-blinkduring the hold and the restanimation: nonewithopacity: 1on each typed or erased characteris-demo-keyingCSS rule fails both caret specs.Expected "caret-blink", Received "none").