Skip to content

feat(ui): Base UI primitives and the upstream simplification batch (upstream batch C) - #33

Merged
theRealBithive merged 21 commits into
mainfrom
feat/upstream-batch-c
Sep 15, 2026
Merged

theRealBithive merged 21 commits into
mainfrom
feat/upstream-batch-c

Conversation

@theRealBithive

@theRealBithive theRealBithive commented Sep 14, 2026

Copy link
Copy Markdown
Owner

What changes in the running service

Ten upstream commits, cherry-picked with -x in upstream order: the seven of QuackbackIO#545 and the three of QuackbackIO#546.

  • Overlay primitives move from Radix to Base UI (upstream feat(ui): migrate overlay primitives from Radix to Base UI QuackbackIO/quackback#545): dialog, alert dialog, popover, dropdown menu, select, tooltip, tabs, switch, checkbox, radio group, slider, collapsible, scroll area and avatar are now @base-ui/react; the sixteen @radix-ui/* packages and radix-ui leave 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.
  • Simplification batch (upstream refactor: simplification batch, dead-code sweep and shared building blocks QuackbackIO/quackback#546): dead-code sweep, shared taxonomy validators, ticket slugs and attribute schemas, shared nextPosition, attribute types and webhook safe-dispatch. No behaviour change intended by upstream.
  • The branding checkbox in the billing checkout builder gets its own label path; the file is dormant here, the checkbox primitive fix is what we take.

Fork seams

  • The slash, emoji and mention list suites upstream added render through renderWithIntl, because those lists read useIntl in this fork.
  • The emoji menu's "Recent" / "Popular" headings are catalogued (ui.editor.emoji.*, nine languages).
  • GRAPH.md and MODULE-STATE.md re-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.ts grew 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/ids has had a typecheck script that no job ran, so its 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.
  • MUTATION_BUDGET_SECONDS was sized for 15 files and 714 mutants. The manifest
    now 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.ts are recorded as equivalent rather than
killed. 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 mutation
swings 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.

Gate Result
mutation PASS — 16 files, 1,037 mutants, 1,009 killed + 10 by timeout, 18 excused, score 98.26%
diff-coverage PASS — 155 files, 3,097 lines judged, 0 never executed
typecheck clean — web, workspace probe, ids, db, widget, email, scripts
lint exit 0, pre-existing warnings only
i18n PASS

The 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.ts signs its own SHA-1 fixture, and
the system OpenSSL on Fedora refuses to produce one. It is red on main too.

🤖 Generated with Claude Code

MKindshofer and others added 21 commits September 14, 2026 20:00
…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>
@theRealBithive
theRealBithive merged commit 6737c10 into main Sep 15, 2026
11 checks passed
@theRealBithive
theRealBithive deleted the feat/upstream-batch-c branch September 16, 2026 09:03
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.

3 participants