Skip to content

feat(v2-ui): integrate v2 schema/review pages into the app shell#232

Merged
JonnyTran merged 14 commits into
developfrom
polish/v2-ui-shell-integration
Jul 20, 2026
Merged

feat(v2-ui): integrate v2 schema/review pages into the app shell#232
JonnyTran merged 14 commits into
developfrom
polish/v2-ui-shell-integration

Conversation

@JonnyTran

Copy link
Copy Markdown
Member

What & why

Integrates the v2 vertical-slice pages (schemas list, schema detail, schema settings, reference review — merged in #230) into the app shell: InternalPage + AppHeader + workspace-aware breadcrumbs, design-system components (BaseButton, status badges, tokenized controls) replacing ad-hoc primitives, and consistent empty/error states. Executes docs/superpowers/plans/2026-07-19-v2-ui-shell-integration.md.

The plan was presentation-only, but getting the v2 e2e suite green surfaced three real bugs (one frontend regression, two backend) plus one prerequisite that had to be brought in — all included here and called out below.

Changes

Frontend — shell integration (presentation)

  • New composables useEnsureWorkspaces (hydrates the workspace store on direct URL load of v2 routes — fixes the "empty schemas on hard reload" state) and useV2Breadcrumbs (Home › workspace › Schemas trail). TDD, 4 tests.
  • New V2Empty + V2StatusBadge shared components. TDD.
  • Schemas list / detail / settings and reference-review pages wrapped in the shell with breadcrumbs, design-system controls, and status badges. All e2e/v2 selectors ([data-question], [data-reference], [data-record], [data-test=...]) preserved.

Fixes required to pass the e2e gate

  • 🐛 BaseButton to prop type (3ac16521d): was type: String | Object — a JS bitwise-OR that evaluates to 0, a malformed prop type. Vue dev-mode validation then throws "Right-hand side of 'instanceof' is not an object" whenever :to is passed, which broke the schema-detail render and was latent app-wide (DatasetCard, UserSettingsHeader, QuestionsForm). Fixed to [String, Object].
  • 🐛 Backend prefilter kwarg (4fa777343): FTS + scalar-filter search called AsyncFTSQuery.where(clause, prefilter=True), but async LanceDB 0.34 where() has no prefilter kwarg → TypeError500 on every text+filter search. Fixed to query.where(clause) (postfilter) + regression test.
  • 🐛 Frontend search input (fa5dd96f1): BaseSearchBarBaseInput debounces its update:modelValue emit while re-rendering a controlled :value, dropping characters under fast typing and losing Playwright fill() entirely. Replaced with a tokenized native <input v-model @keyup.enter> on the detail page.
  • Breadcrumb strict-mode fix (reference leaf no longer duplicates the <h1> DOI); dead #c00 token fallback removed.

Prerequisite cherry-pick (⚠️ see note)

  • cefe95e42 cherry-picks 551d0bd9f (FileObjectResponse.response: HTTPResponse → Any) from fix/v2-fileobjectresponse-streaming-body. The plan names it as a required base, but this branch wasn't cut from that branch. It is unmerged with no open PR, so bundling it here is how it reaches develop. If it later gets its own PR, expect a duplicate/empty patch — reconcile before merging that one.

Test evidence

  • Unit: 868 pass (npx vitest run)
  • Types: npx nuxi typecheck exit 0
  • Lint: npm run lint clean (only pre-existing i18n false-positive warnings)
  • Backend: ruff clean; test_fts_search_with_scalar_filter + test_files regression tests pass
  • v2 e2e: 5/5 serial (npx playwright test --project=v2 --workers=1) — the CI-intended config (playwright.config sets workers: CI ? 1 : undefined).

⚠️ e2e parallel flakiness (pre-existing, not from this PR): the local default npm run e2e:v2 runs fullyParallel and can flake on search-roundtrip — all 5 specs share one backend schema and the review specs' concurrent :rebuild-index transiently 500s a search. Serial (CI's path) is clean. Follow-up: harden the #230 fixtures with per-spec schema isolation. No CI workflow runs this suite (needs a live seeded stack).

Reviewer notes / risk

  • BaseButton prop-type fix touches a shared base component — low risk (strictly more permissive), but it changes behavior for all :to consumers, so worth a glance.
  • Two backend files change on a nominally-frontend branch (the prefilter fix + the FileObjectResponse cherry-pick). extralit-server.yml (pytest) will exercise them in CI.

JonnyTran added 13 commits July 18, 2026 16:22
…ot the hook

GetSchemasUseCase and GetReferenceReviewUseCase were typed as `typeof useXxx`
and called `this.storage()`, but ts-injecty resolves a hook dependency by
*calling* it and injecting the returned store object (same contract as v1's
GetWorkspacesUseCase). At runtime `this.storage()` threw
"this.schemasStorage is not a function", which the empty catch swallowed and
surfaced as "Could not load schemas." on a 200 response.

Use the injected value as an object (drop the `()`) and type it as
ReturnType<typeof useXxx>. The unit tests passed the raw hook function —
bypassing the DI contract — so they missed this; update them to pass the
resolved store (useXxx()) as DI does.

Verified live: the seeded e2e_v2_slice schema now renders in the table.
Surfaced by the first end-to-end run of the v2 frontend e2e.
…rumb strict-mode collision

- BaseButton `to` prop was declared `type: String | Object` (bitwise-OR evaluates to 0),
  a malformed prop type that threw 'Right-hand side of instanceof is not an object' during
  Vue dev-mode prop validation whenever a :to value was passed. This broke the schema-detail
  page render (stuck spinner, no records) and was latent in DatasetCard/UserSettingsHeader/
  QuestionsForm. Fixed to [String, Object].
- Reference-review breadcrumb rendered the bare reference, colliding with the page <h1>
  'Review — {reference}' so getByText(reference) matched two nodes (e2e strict-mode
  violation). Leaf is now the static 'Review' label; the <h1> still carries the reference.

Unblocks e2e/v2 auth-smoke + slashed-reference (both green).
v2 record bulk-upsert reads the pandera schema body from S3 via
FileObjectResponse.response, which was annotated urllib3.HTTPResponse
(from the old minio client, PR #149). aiobotocore returns the body as a
StreamingChecksumBody, so pydantic's is_instance_of check rejected it and
every v2 upsert 422'd. Relax the annotation to Any (consumers only stream
the body via .read(); the existing validator still guards None) and add a
regression test.

Surfaced by the first end-to-end run of the v2 frontend e2e seed.
… search

Combining an FTS query with a scalar filter called
AsyncFTSQuery.where(clause, prefilter=True), but async LanceDB (0.34) where()
takes only the predicate — the sync-only prefilter= kwarg raised TypeError, so
every text+filter search 500'd. Scalar filters over an FTS match set apply as a
postfilter, correct for the materialize-then-page model. Add a regression test
covering FTS+filter narrowing and the non-matching-token empty case.

Surfaced by the v2 search-roundtrip e2e (status filter + query).
…rops fill()

BaseInput debounces its update:modelValue emit (lazyEventEmitter, 100ms) while
re-rendering a controlled :value, so the DOM value resets to the stale model on
each keystroke — losing characters under fast typing and dropping Playwright
fill() entirely (the emitted value arrives as ''). A tokenized native input with
v-model + @keyup.enter is immediate and lossless, and matches the search-roundtrip
spec's fill + press('Enter') contract. Styled with design tokens for visual parity.
The HANDOVER.md file has been deleted as it contained outdated information regarding PR #214 and its verification results. This cleanup helps maintain the repository's documentation relevance.
@JonnyTran
JonnyTran requested review from a team as code owners July 20, 2026 06:22
@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
extralit-frontend Ignored Ignored Preview Jul 20, 2026 7:00am

@JonnyTran
JonnyTran merged commit 52eab55 into develop Jul 20, 2026
1 of 3 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