feat(ui): Base UI primitives and the upstream simplification batch (upstream batch C) - #33
Merged
Merged
Conversation
…ract line, P7 The six tests that pin what the status reports for a connected, enabled or outdated install were written under P3, which only promises that a database failure is reported as such. P7 now names the guarantee they hold - the status reports what the site sent - and the header says when it was added and that it was confirmed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replace @radix-ui/* with @base-ui/react across the shared primitives (dialog, popover, menu, select, tooltip, tabs, switch, checkbox, collapsible, sheet, slider, avatar, badge, button, form, label, scroll-area), with an asChild composition helper, per-component usability e2e, and a hydration wait helper. Also fixes crossed-fingers emoji resolution/shortcuts, emoji recents, and two migration regressions found in browser testing (dead reply composer, sticky radio menus). Verified: 15k unit green (1 pre-existing vendor-parity fail), typecheck/lint clean, base-ui/boards/inbox/emoji e2e green. (cherry picked from commit 0935071)
Clear the composer in place via a new RichTextEditorHandle.clear() instead of remounting on every send (which destroyed the focused node and dropped focus to body, where TipTap autofocus never recovered it). The composer also stays editable through the send flight — double-send was already guarded — so focus never leaves, mid-flight typing lands in the fresh composer, and error restores skip when newer typing exists rather than clobbering it. (cherry picked from commit ee44b03)
Subscribe MenuBar and BubbleMenuContent via useEditorState so only the button whose active state changed re-renders (was: the whole toolbar plus dialog chrome on every transaction), and drop the editor.can().chain().focus() probes from render. Defer full zod validation to submit in the six post/article/ changelog dialogs (validate-on-type was pure per-keystroke waste; submit still validates everything). Measured fast-typing 133 chars at 15ms intervals: p95 keydown to frame 63ms -> 41ms, p50 22ms -> 16ms, zero dropped keys. (cherry picked from commit ebe6307)
- select: stabilize the label-registry context (ref-only writes, microtask re-render bumps, live lookup) so grouped and async options can no longer feed a synchronous update loop. - inbox: preserve a failed send below newer mid-flight typing instead of dropping either; failed content is never lost. - checkbox: skip the label-loop stopPropagation for decorative row-embedded boxes without their own onCheckedChange, so row clicks on the box still toggle. - suggestion popups (slash/emoji) and the @ mention picker now flip below the caret near the top of the viewport instead of rendering off-screen. (cherry picked from commit 94dcab8)
- thread-restore: merge the failed draft into the editor JSON as plain paragraphs (markdown-only merge was invisible after the key-bump remount), markdown kept in sync for next edit. - onSuccess focus: only hand focus back when it is still on the send control or inside the composer (isSendControlFocused), so a mid-flight click elsewhere is never yanked back. - checkbox: data-in-label opt-out on 20 native-label sites with their own onCheckedChange, keeping label activation working. (cherry picked from commit 48045cc)
- thread-restore: merge the snapshot's own JSON nodes verbatim (full fidelity, no parsing) instead of flattening to paragraphs. - emoji recents: guard the getItem read itself against blocked storage, not just the localStorage access. (cherry picked from commit c926b53)
The box is a native button, so wrapping it in a label created two toggle paths that raced (fireEvent vs label re-dispatch flaked the suite under shard load). The row is a div now; only the title text is a label via htmlFor, so every clicker takes exactly one path. (cherry picked from commit 9a92f53)
…locks Batch A: remove dead code (empty settings schema, sanitizeImageSrc and getNestedClaim aliases, getHelpCenterBaseUrl, test-only category helpers, resolve-sso-role and coerce server shims) and dedupe the help-center ancestor chain and isRecord guards into shared modules. Batch B: centralize repeated taxonomy zod schemas (hex color, name, pagination) in lib/shared/schemas/taxonomy.ts and apply them across statuses, tags, changelog categories, and REST routes. Batch C: extract a shared attribute-definition service factory used by the user and company attribute services, with a parity test suite, and regenerate the affected policy goldens. Batch D: generate lib/shared/permissions.ts from packages/db via a new db:permissions script, with a freshness assertion in the drift test. No behavior change; all messages, key order, and permissions preserved. Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit 4aa245e)
…chemas Extract assertTrimmedName/assertHexColor and HexColorFormatSchema so tag, status, changelog, and ticket services stop restating the same rules. Finish the attribute-definition follow-through (shared Zod schemas, company-attributes fn module) and collapse the ticket unique-slug copies plus the cloned TypeID-as-text columns. positionCaseSql now covers changelog category and status-component reorders. Error messages, limits, and permissions are unchanged. Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit e31eb05)
Extract nextPosition for append-at-end creates, collapse user/company attribute types onto one record shape, and finish the leftover taxonomy Zod copies (optional hex, entity id, reorder). Post-tag duplicate names use a lower(name) lookup instead of loading every row. Tickets and conversations share makeSafeDispatch for best-effort webhook emits. No behavior change; messages, limits, and permissions are unchanged. Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit 0c92554)
…the fork's own seams Ten upstream commits (QuackbackIO#545 x7, QuackbackIO#546 x3) were cherry-picked with -x; this is what the fork had to do around them. - Tests upstream added for the slash, emoji and mention lists render without an IntlProvider; the lists read useIntl here, so those suites use renderWithIntl. Two new buildExtensions calls go through the intl-supplying helper of their suite. - The emoji suggestion menu's group headings "Recent" and "Popular" arrived as English text in a file the i18n gate checks; they now read ui.editor.emoji.recent and ui.editor.emoji.popular from the nine catalogues. - GRAPH.md and MODULE-STATE.md are re-rendered from this tree: upstream's QuackbackIO#546 removed the assistant -> posts/statuses/tags edges, and the module-state ledger drops the Slack, dormancy and worker sites this fork never carried. - The lockfile is regenerated from the merged package.json: the sixteen @radix-ui packages and radix-ui leave, @base-ui/react ^1.8.0 arrives, and @slack/web-api stays at the version we hold. - UPSTREAM.md: the ten rows read picked, fork #33. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Three fork suites went red on the first CI run of the Base UI picks, one of them only half the time. - The Base UI Select selects on a pointer sequence, not on a bare click event, so the three identity-provider tests that pick a prompt, a client authentication method and a new-account role drive the trigger and the option with userEvent, the way upstream rewrote its own suites. - The Base UI Dialog unmounts its content after the close transition, so the tag dialog's Cancel test waits for the field to be gone instead of asserting it in the same tick. - happy-dom's <label> forwards every click to its control, including a click whose target already is an interactive descendant, which browsers do not do. A Base UI Switch toggles by dispatching a click on its hidden input; inside a <label> that click was forwarded back to the button and the pair looped until the stack overflowed, 200 to 270 rounds, with the toggle landing or not depending on where the stack gave out (12 of 25 renders lost it). The shared vitest setup now gives the label the browser's behaviour, which fixes every label-wrapped switch and checkbox in the fork at once; a new suite pins it across 25 renders and for the label-text path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The ten upstream commits arrived with 465 lines of tests; the fork's contract for them, confirmed on 2026-09-15, has 56 numbered guarantees in eleven groups, and this commit adds the suites that pin them: 527 tests across 53 new or extended files, all green, typecheck and lint clean. - G1–G13 (was E in the confirmed list; relettered because two editor suites already carry an earlier batch's E1/E2): suggestion-list keys, emoji recents and ranking, query highlighting, emoji lookup, the `:shortcode:` rule, list selection, the recent-section label, and the popup positioner lifecycle. Properties: MRU recents against an oracle, rank ordering over a generated catalogue, query-case non-interference, highlight conservation. - U1–U5, F1–F2, M1–M2: the Base UI Select label registry, Slider values, overlay triggers with an explicit render element, popover refs, menu groups and submenus; the five editor-backed forms and two boolean checkboxes; every migrated admin menu entry fires and does what its label says. - C1–C4: the inbox composer's focus hand-back, in-place clear, and the failed-send restore and merge-below-new-typing paths, with a property over both drafts' nodes around the separator. - T1–T8, W1–W2, S1, I1: the shared name and colour validators with each entity's own message, the request schemas, underscore slugs and the lowest-free-suffix rule (property), next-position, the per-entity messages through every consolidated service, safe dispatch, plain records, and a TypeID column refusing a non-string. - A1–A8, R1–R3: one rulebook for user and company attribute definitions (property over both services), normalisation, currency-code lifecycle, conflict and not-found mapping, database failures as server errors, the server functions' authorization, and the permissions mirror's order, categories and generator errors. The mutation manifest grows by fourteen graded files (the pure modules of this batch); the UI wrappers stay ungraded as before. SELF-IMPROVE.md records what the run taught: the happy-dom label loop, the query-key mismatch in the thread suite, the empty v8 text report at 100 %, TipTap's per-keystroke popup rebuild, Base UI's render prop, and the server-function handler capture. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…he checkout Stryker's default `disableTypeChecks` pattern writes `// @ts-nocheck` into every sandbox copy under a src/, lib/ or test/ directory so its own instrumentation does not trip a TypeScript checker. This gate runs no checker, so the line bought nothing, and it changed the bytes of a file a suite reads as data: the permissions drift test compares the committed mirror with the generator's output and failed the dry run on exactly that inserted line — which the gate reported as having graded nothing, on the first run that declared packages/db/src/permissions-mirror.ts. The generated configuration now states `disableTypeChecks: false`, the B10 tests pin it alongside the other settings, and SELF-IMPROVE.md records how the cause was found (a probe inside the surviving sandbox). Nothing in the running service changes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d additions The first mutation run over batch C's fourteen newly graded files left 111 survivors. This closes them where a test can, and adds the five contract numbers the coordinator and the user agreed on after reading that report. What is new, per file: - packages/ids/src/__tests__/drizzle.test.ts grows from 5 tests to 51 and now holds I2 and I3. The UUID pattern is asserted from both sides: the three shapes it must accept (hyphenated, hyphenless, upper case) and twelve near-misses one character class or one segment length away, each of which must reach the TypeID parser and be refused rather than handed to Postgres as a raw UUID. `dataType()`, the nullable column's null/undefined paths, the default generator's minted prefix and the reference column's conversions were all unreachable before, which is why the file scored 42 survivors. - attribute-definition-rulebook.test.ts gains A9, the request schemas the server functions validate against: the five value types and ten currency codes by name, and every length bound asserted from both sides (the longest value still accepted and the first one refused). A limit only means something if it lets the legal case through, and nothing had asserted that. - The reworded F2 replaces the half-sentence about an indeterminate checkbox in seven suite headers. A box in that state cannot be produced through the component's own API, so the clause could not fail and therefore said nothing. Nothing in the running service changes: this commit is tests and test headers only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…vors Three more mutants of the emoji ranking that no test noticed, and the two contract numbers the user confirmed for the composer and the editor handle. - Being remembered outranks starting with the query, and one shortcode that starts with the query is enough beside shortcodes that do not. Both rank classes were only ever exercised through entries that agreed with the class below them, so blanking either rule left every assertion green. - A remembered exact match wins even when it is not the most recently used glyph. Every recency test until now put the remembered entry at position 0, where "is it remembered" and "is it the newest" cannot be told apart. - C5 (the translation refusal offers Send untranslated, which re-sends with translation skipped and hands focus back) and G14 (the handle's clear empties through the live editor in place, and is a no-op once the editor is gone) are copied verbatim into their suite headers and named by the tests that hold them. The two existing "Send untranslated" tests carried (C2); they are now (C5) and assert that exactly one send reached the server before the offer was taken, so the refusal is not vacuous. - next-position.ts now declares its DB-backed suite in the mutation manifest. The suite existed and the manifest did not name it, so the gate graded the file against the mock-based suite alone and reported the SQL fragment's two mutants as survivors. Nothing in the running service changes: tests, test headers and the manifest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… reach Five of the six sit on the comparator's `if (aRecent !== -1 || bRecent !== -1)`. The second operand of that `||` can only decide the entry when the first one is false, which is exactly when `aRecent === -1` — and that is the case the block answers with its own first statement, `return 1`. So every mutation of the right half swings the comparator between 0 and 1 and never between 0 and -1, and a stable sort reads 0 and 1 the same way: "do not put a ahead of b". Confirmed by brute force rather than argued, because two of the mutants also make the comparator inconsistent — 540,000 arrangements (60,000 each at lengths 2, 3, 4, 5, 8, 16, 40, 80 and 200) sorted identically under V8. The sixth is the SSR guard in `storage()`, where skipping the check throws a ReferenceError inside the adjacent try and the catch returns the same null. Two of those addresses are shared with mutants that a test does catch: the manifest addresses a record by file, mutator, replacement and line text, and a line with two operands can carry the same mutation twice. The records say so and name the tests that hold the killed siblings, because a record is only consulted for a mutant that survived and one that matches no survivor fails the run as stale. Two gaps around the gate itself, both found by looking rather than by a failure: `packages/ids` has a `typecheck` script and no job ran it, so the id helpers and their suite were the one piece of workspace TypeScript nothing compiled. The `check` job now runs it, and it immediately rejected a protected-property cast in the new drizzle suite, which is now routed through a named helper like the two beside it. The mutation budget was sized for 15 files and 714 mutants. The manifest now reaches 16 files and 1,037, measured at 8m30s locally, so 900 seconds on a four-core runner is close enough to the edge to fail as "nothing measured". The budget goes to 1500 and the job timeout to 30 minutes. Nothing in the running service changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tant address Three counters move. Flaky suites under parallel load reaches ten: four coverage shards produced six failures across three of them, every one green when re-run alone. The Fedora SHA-1 fixture reaches four, and this time it cost fourteen minutes twice over — three of four shards ended red, `coverage/` kept only the one that passed, and the gate graded that quarter of the repository without saying so. A mutation survivor addressed by line reaches three, with the case the previous workaround does not cover: an upstream line whose two operands carry the same mutation, where splitting the condition would diverge a file the fork otherwise carries unchanged and would not remove the collision anyway. One new entry: rewriting a `describe` block in place dropped an unrelated test from the block and the suite stayed green, because no test count is asserted anywhere and neither gate can see a deleted test. One resolved entry, and the section for it. `packages/ids` has always had a `typecheck` script that no job ran, so its helpers and their suite were the one piece of workspace TypeScript nothing compiled — found because the first run of it rejected a cast that had been green for a session. Nothing in the running service changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The comment beside MUTATION_BUDGET_SECONDS guessed that a four-core runner would be slower than the laptop the 8m30s came from. It is not: the job finished in 6m08s including install and migrate. The number stays at 1500, the reason it is there changes from a guess about this batch to headroom for the next manifest entries. Nothing in the running service changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ites The gate re-ran `next-position.ts` against only its mock-based suite while the DB-backed one sat unlisted beside it, and reported the SQL fragment's two mutants as survivors. Sweeping the whole manifest the same way found one more, pre-existing and so far invisible: `gitlab/server/inbound.ts` has an `inbound.properties.test.ts` its entry does not name, and the file is untouched so nothing has graded it. Left unfixed on purpose — adding a suite to an entry is an assertion about what those suites hold, and this branch has no run that would measure it. Nothing in the running service changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.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.
What changes in the running service
Ten upstream commits, cherry-picked with
-xin upstream order: the seven of QuackbackIO#545 and the three of QuackbackIO#546.@base-ui/react; the sixteen@radix-ui/*packages andradix-uileave the tree. The checkbox is a native button, which is why the branding-checkbox commit below matters. The inbox composer keeps focus across send, and the post and comment composers do less work per keystroke (toolbar state is a subscription, not a re-render). The emoji picker recommends recent and popular emoji.nextPosition, attribute types and webhook safe-dispatch. No behaviour change intended by upstream.Fork seams
renderWithIntl, because those lists readuseIntlin this fork.ui.editor.emoji.*, nine languages).GRAPH.mdandMODULE-STATE.mdre-rendered from this tree.What the branch adds beyond the picks
The picks arrive with a confirmed contract list (A1–A9, C1–C5, F1–F2, G1–G14,
I1–I3), copied verbatim into each test module header, and every test names its
number.
packages/ids/src/__tests__/drizzle.test.tsgrew from 5 tests to 51,the attribute-definition rulebook to 53, and the emoji ranking suite to 37.
Two gaps around the gates themselves, both found by looking rather than by a
failure:
packages/idshas had atypecheckscript that no job ran, so its helpersand their suite were the one piece of workspace TypeScript nothing compiled.
The
checkjob now runs it, and it immediately rejected a protected-propertycast in the new drizzle suite.
MUTATION_BUDGET_SECONDSwas sized for 15 files and 714 mutants. The manifestnow reaches 16 and 1,037, measured at 8m30s locally, so the budget goes to
1500 and the job timeout to 30 minutes. An exceeded budget fails as "nothing
measured", not as a low score.
Six mutants in
emoji-recommendations.tsare recorded as equivalent rather thankilled. Five sit on the comparator's
if (aRecent !== -1 || bRecent !== -1),where the right-hand operand can only change the answer when
aRecent === -1—which is the case the block answers with
return 1, so every such mutationswings the comparator between 0 and 1 and never between 0 and -1, and a stable
sort reads those the same way. Confirmed over 540,000 arrangements rather than
argued, because two of them also make the comparator inconsistent. Two of those
addresses are shared with mutants that a test does catch; the records say so and
name the tests.
Gates
Run locally against
main, all green.mutationdiff-coveragetypechecklinti18nThe full suite was run as four sharded coverage passes. Six tests failed across
three shards and all six pass when re-run alone, which is the flakiness under
parallel load that SELF-IMPROVE has been counting. One failure is not a flake
and is not this branch:
sns-signature.test.tssigns its own SHA-1 fixture, andthe system OpenSSL on Fedora refuses to produce one. It is red on
maintoo.🤖 Generated with Claude Code