fix(spec-parity): the fifteen Tier-2 spec values render instead of validating into nothing (#2942) - #3008
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
… into nothing (#2942) Every row below is the #2897 shape — validates at authoring time, renders nothing (or a dead control), no test fails and no warning fires: - UserFilters `element: 'toggle'`: `default: return null` deleted the ENTIRE filter bar for stored toggle configs. The existing-but-unreachable ToggleFilters branch is wired; authoring stays untypeable (ADR-0053) while stored metadata keeps rendering (spec ADR-0047 §3.4a). - UserFilters `date-range` / `text`: dead "No options" popovers become a from/to date pair (emits >=/<= bounds) and a contains search input. - useAnimation: preset/easing maps re-keyed to the spec's underscore vocabulary (+ rotate/flip via usePageTransition's classes); hyphen dialect and `scale-fade` stay accepted for stored configs. The `EASING_MAP[easing] || easing` fallthrough no longer emits invalid CSS. - NotificationContext: displayType materialized (spec default toast, legacy `modal` presents as alert) and the unions now match NotificationTypeSchema / NotificationPositionSchema instead of claiming to. - useNavigationOverlay: the spec `size` buckets resolve to viewport-clamped widths off app-shell too (explicit `width` still wins; `auto` stays host-derived). - Both ThemeProviders: `mode: 'auto'` follows the OS instead of adding a dead `auto` class that locked the light theme; `system` kept as the pre-spec spelling. - AdvancedChart: the single-value families (gauge/solid-gauge/metric/kpi/ bullet) render the measure as a number, table/pivot name their owning component, unknown types are named — never the bar SHELL with null series marks that was indistinguishable from an empty dataset (reachable via ChartRenderer's `schema.chartType ?? spec.chartType` bypass). - Timeline: the spec `scale` key is read at last (legacy `timeScale` kept); hour/quarter/year generate real gantt header buckets instead of a blank axis. - Toaster: position (all six spec values) and `limit` reach sonner instead of being discarded by a bare `<SonnerToaster />`. - useSpecGesture: the DECLARED `config.type` drives recognition — pan/drag/rotate/double_tap no longer collapse to tap; useGesture gains real double-tap (two taps, not one) and two-touch pinch/rotate deltas. - ReportViewer: `aggregation: 'distinct'` computes a distinct count instead of a blank summary cell. - FieldEditWidget: inline resolution goes through the form's alias table, so `json` gets the code editor, `tree` the lookup picker, and composite/ record/repeater/video/audio/autonumber resolve to their documented exclusions; ObjectGrid's editability gate consults the same contract, so a `composite` cell is read-only instead of a value-corrupting text box. - FilterBuilder: $startsWith/$endsWith/$null/$exists become authorable (startsWith/endsWith/isNull/isNotNull/exists/notExists) and round-trip through condToMongo/kvToCondition — every FieldOperatorsSchema token is now reachable from the UI. The export-menu row (PDF silently downloading nothing) was fixed concurrently by #2999, which this branch rebases onto and defers to. Each fix lands with a spec-parity guard per the #2897 template; `fields`, `mobile`, `plugin-charts` and `providers` gain the `@objectstack/spec` devDependency that makes those guards possible (no second zod peer variant materialized — verified in the lockfile). Refs #2942, #2901 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
xuyushun441-sys
force-pushed
the
claude/tier2-spec-silent-absent
branch
from
July 30, 2026 09:24
265d762 to
0bab7cb
Compare
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This was referenced Jul 30, 2026
Closed
Closed
Closed
os-zhuang
added a commit
that referenced
this pull request
Jul 30, 2026
…3014) (#3071) `NotificationProvider` handed every notification to the host's `onToast` delegate regardless of `displayType`, so all five spec types presented as a toast — an author picking `banner` or `inline` got a transient overlay. #3008 made the value reach the delegate; nothing branched on it. Each type now routes to its own surface: toast -> the host's `onToast` delegate (unchanged) snackbar -> <NotificationSnackbar /> bottom-anchored, one at a time, 1 action banner -> <NotificationBanners /> page-width strip, in the content flow alert -> <NotificationAlerts /> blocking acknowledgement, FIFO queue inline -> <NotificationInline /> in place, at the raising surface Banner/inline placement is the host's — they are not overlays — so the context exposes the items (`useNotificationsByPresentation`) and the surfaces subscribe. `alert` renders through the AlertDialog primitive, NOT the action system's ModalHandler: that handler resolves a page and reports an ActionResult, while a notification alert has no schema, target or result. Auto-dismiss follows the presentation: toast/snackbar stay transient, banner/alert/inline are persistent unless `duration` is set explicitly (a persistent banner used to evaporate on the shared 5s toast timer). `NOTIFICATION_PRESENTATIONS` is `Record<NotificationPresentation, …>`, so a new spec enum member fails type-check until its presentation is decided; the parity test asserts the table covers `NotificationTypeSchema` exactly and that no two types share a surface. Raising a surface-rendered type with nothing mounted to present it warns in dev instead of vanishing. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
os-zhuang
added a commit
that referenced
this pull request
Jul 31, 2026
…instead of forked or ignored (#3014 follow-up) (#3085) The last of the notification contract. After displayType (#3071) and icon (#3076), four gaps of the same family were left: - the config was 3/4 inert: only `defaultDuration` was ever read, while `maxVisible` / `stacking` were carried and ignored and NotificationBanners capped at a hard-coded 3 of its own; - its field names forked from `NotificationConfigSchema` (`position` vs `defaultPosition`, a renderer-local `stacking` boolean, no `pauseOnHover`); - a notification could not declare a `position` at all — the #3008 parity guard asserted the position VOCABULARY while nothing positioned anything by it; - `NotificationActionButton.variant` was the shadcn Button vocabulary (`default | destructive | outline`) under a spec-shaped name, forking `NotificationActionSchema.variant` (`primary | secondary | link`). Positioning resolves as `notification.position ?? config.defaultPosition ?? nothing`, and "nothing" is a real answer: declared → the surface pins itself there and `presentNotificationToast` passes it per-toast so the contract beats the container; undeclared → the surface keeps its own anchor, or defers to the host's toast chrome. That asymmetry is the decision — the sonner container also serves toasts that are NOT spec notifications (the action runtime's own `toast.*` calls), so it stays the fallback authority for placement, never a competing one. Hence `defaultPosition` has no fabricated default: "the host didn't say" has to be representable. `maxVisible` / `stackDirection` now drive every stacking surface through one shared `visibleNotificationStack`; `pauseOnHover` holds a transient timer and resumes it with the time it had left, which needed the provider to track live timers instead of fire-and-forget setTimeouts. Legacy spellings still resolve: `position` folds into `defaultPosition`, `stacking: false` reads as `maxVisible: 1`. `onToast` gains the resolved config as a second argument (one-arg handlers are unaffected), and the spec-parity guard gained the action-variant vocabulary — the one notification enum it did not cover. Verified in the running console, in one frame: an undeclared toast stays where the sonner container puts it (bottom-right), a toast declaring `top_left` moves there, and a snackbar declaring `top_right` leaves its bottom anchor. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 5 <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.
Closes #2942 (Tier 2 of the #2901 audit — spec values that validate and then render nothing; audit doc). Follows #2993 (Tier 1).
All 15 rows re-verified against current main before touching anything (post-#2989/#2940 the filter-operator row had narrowed to 4 tokens; everything else was live). Two rows turned out worse than filed: the timeline renderer read its own
timeScalekey so even day/week/month spec configs were ignored, and the report-aggregation vocabulary is the types-localdistinct, not the issue'sunique.The fifteen fixes
element: 'toggle'deleted the filter bardefault: return null— the spec promises stored toggle configs keep rendering, the renderer shipped the one artifact that says nothingToggleFiltersis wired. The split is deliberate: authoring stays untypeable (ADR-0053's correct-by-construction removal, pinned byphase2-schemas.test.ts), rendering honors stored metadata (spec ADR-0047 §3.4a)|| BarChartinto a shell whose series marks all returned null — indistinguishable from an empty dataset, reachable viaChartRenderer'sschema.chartType ?? spec.chartTypebypassPRESET_CLASSESkeyed in hyphens vs the spec's underscores; rotate/flip absentusePageTransition, the sibling that always had all nine); hyphen dialect +scale-fadestay accepted for stored configsEASING_MAP[easing] || easing→animationTimingFunction: 'ease_in_out', silently dropped by the browsercubic-bezier(…))mode: 'auto'locked the light themesystemonly;classList.add('auto')matches no Tailwind variantautofollows the OS preference in both providers;systemkept for stored valuesscaleblanked the axistimeScalekey (specscaleignored for ALL six values); hour/quarter/year generated zero headers<SonnerToaster />bare,inputs: []limitreach sonner; registry inputs declareddisplayTypestored, never read; the union claimed spec alignment while carryingmodaland missingalert/inlinemodalpresents asalert);displayTypeis materialized so the presenting delegate can branch. Distinct banner/inline presenters remain the documented follow-up — this PR closes the contract halfnavigation.sizeignored off app-shellwidthplugin-view'soverlayWidthFor;widthstill wins;autostays host-derived (needs field counts only schema-aware hosts have)date-range/textfilters were dead controls>=/<=bounds) and a commit-on-Enter contains searchuseSpecGesturenever readconfig.type; branched on sub-object presenceuseGesturegains real double-tap (two taps within 350 ms — it used to fire per tap) and two-touch pinch/rotate deltasdistinct→ blank celldefault: return ''computeReportAggregationwith aRecord<ReportAggregationType, …>guard so a new union member fails type-check until computedjson/composite/record/repeater/tree/video/audio/autonumberwere in neitherEDIT_WIDGETSnor the exclusion set — the guard iteratedFORM_FIELD_TYPES, which alias-only spec spellings never enterjson→ code editor,tree→ lookup picker, the rest resolve to their documented exclusions — and ObjectGrid's editability gate consults the same contract, so acompositecell is read-only instead of a value-corrupting text box. New guard iterates the specFieldType(49 members, all decided)$startsWith/$endsWith/$null/$existsaccepted byFieldOperatorsSchema, offered nowhere (post-#2940/#2989 remainder)FieldOperatorsSchema.shapeand asserts every token is emittableGuards
Ten parity/coverage suites (~100 new tests), all per the #2897 template — each pair fails the moment the spec or the renderer moves alone. Prerequisite devDeps:
fields,mobile,plugin-charts,providersgain@objectstack/spec(lockfile checked — no second zod peer variant materialized; the components trap from #2993 did not recur).Verification
pnpm build43/43;tsc --noEmitclean on all 13; eslint 0 errors on every touched file.Refs #2901. Remaining tiers: #2943 (loud failures), #2944/#2945 (forks & vocabularies).
🤖 Generated with Claude Code