You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Align shared menu focus, keyboard navigation, submenu entry/exit, and form-panel semantics; retain ordinary input editing and native link activation.
Keep Canvas search focus and active rows aligned, localize search feedback in English and Chinese, and expose toolbar pressed states without changing the intentional main/context icon stroke distinction.
Group search results by node and field, showing each field heading once.
Hand Note content matches to preview-local find/highlight without stealing focus, and reveal cached or paginated older Question turns before locating the requested conversation occurrence.
Preserve independent highlight ownership and account for nested panel clipping when scrolling matches into view.
Fix the two review findings: vertical navigation stays in the parent menu at a submenu trigger, and ArrowRight can enter an already hover-opened submenu.
Update the corresponding architecture documentation and regression tests.
Validation
Repository-level pnpm typecheck
Repository-level pnpm format (no additional changes)
Browser validation with actual production components and synthetic Note/conversation data: field grouping, second Note occurrence, nested scrolling, cached older turns, two simulated older-history pages, later conversation occurrence, and the reviewed submenu keyboard cases.
Scope and limitations
Five focused commits replayed onto current main. The inherited node-placement commit is deliberately excluded.
Browser component checks are not a real-backend end-to-end search test.
Conversation navigation retains the existing bounded timeout and reports an unavailable match rather than silently jumping to another conversation occurrence; very long histories can require a follow-up targeted-address optimization.
No contrast, design-token, dependency, or main/context icon-stroke changes.
Unify shared menu focus and keyboard behavior, preserve custom panel semantics, and synchronize canvas search activation with focused results. Add regression tests and document the interaction contracts.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Translate search status messages, conversation timeout feedback, and all result field labels in English and Simplified Chinese. Cover language switching and localized result states with rendered tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep navigation and placement aria-pressed states aligned with visual activation through the shared SplitSelect primary action. Add state-transition tests and document the intentional main/context toolbar icon stroke tiers without changing their styling.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Group results by field, hand Note matches to preview find, reveal and load older Question turns, and preserve highlight and focus ownership across surfaces.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep vertical navigation in the parent menu and let ArrowRight enter hover-opened submenus. Cover both reviewed keyboard regressions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preserve virtualized result focus, reveal menu edges, await PDF indexing and exact asynchronous Note matches, and separate complete navigation counts from bounded highlights.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The new tabIndex={-1} default breaks CanvasZoomMenu's custom dialog, whose handler relies on native Tab traversal between the percentage input and action buttons. Tab from the input now skips every action, and the boundary handler does not dismiss because it expects focus on the last action (CanvasZoomMenu.tsx:109–132). Keep the shared menu default, but add explicit tabIndex={0} overrides to that dialog's five action items.
Resolve question content ordinals within their originating message
Question content ordinals are local to the node body, not the whole conversation. Saving a Chat copies its first cached user message into that body (ChatPanel/index.tsx:876–891), which can be a recent message rather than the thread's first prompt. After this branch loads older turns, content occurrence 0 can therefore select an unrelated earlier match. Resolve content matches within their originating message, or keep them separate from whole-thread ordinal navigation until that address is available.
Prevent stale PDF search adapters from consuming new handoffs
A handoff to an already searched PDF can consume the previous query's adapter. useRegisterPreviewSearchAdapter publishes registration in a passive effect, so this render still sees the old match count and navigation callback when reveal() changes the query. It can navigate the old query or report no matches, then consume the new request before the replacement adapter arrives. Include query identity in the adapter contract and wait for a matching registration before consuming the nonce. Add a regression for two successive handoffs with different queries to the same PDF.
Reject out-of-range PDF match ordinals instead of clamping
If the completed PDF index has fewer matches than the requested ordinal, this clamp silently navigates to the last available occurrence and consumes the request. This can happen after document changes or when indexing skips a malformed page. Treat an out-of-range ordinal as unavailable and show the existing notification instead of selecting another match.
Restore Zoom dialog tab stops, resolve Question body ordinals within a unique source prompt, and reject stale-query or unavailable PDF navigation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addressed the four previously missed findings from the latest Copilot review in aa5a120, after reproducing all four on the prior revision:
Zoom dialog: explicitly retain native Tab stops on its five action items. Real-browser Tab traversal now visits all five and closes at the boundary.
Question content: reuse the shared conversation reveal/paging scheduler, but resolve body-local ordinals only inside the unique complete source prompt. A dedicated prompt-text marker excludes quoted comments, skill chips, interpretation text and attachments. Missing or duplicate source prompts report unavailable rather than selecting another message. Clicking an actual search result with older history loading now scrolls to the recent source message, not the unrelated older match.
PDF query identity: adapters carry their query, and handoffs wait for a matching registration. A real four-page PDF switching alpha to beta now consumes the request only with the beta adapter and scrolls to page 4, rather than page 1.
PDF ordinal bounds: unavailable ordinals notify instead of clamping. Browser-verified requesting beta occurrence 4 when only one exists: no navigation, localized unavailable notification.
Validation: 329 tests across 19 targeted files passed; repository pnpm typecheck, pnpm format, pnpm lint:fix, and the Web production build passed. Related architecture documentation updated. Temporary fixtures and servers removed; no merge performed.
Use the same normalized query as the Canvas request. searchStore.setQuery retains the input verbatim but calls runSearch(query.trim()). Searching needle can therefore return a Note containing only needle, then open its find bar with zero matches and time out. Trim the query passed to reveal and add leading/trailing-whitespace coverage.
Preserve Canvas handoffs during StrictMode effect replay
The app mounts under StrictMode (main.tsx:38). On a cold preview mount in development, effect replay runs this cleanup while the Canvas-search handoff already owns this node. close() clears the pending request before asynchronous Note content or the PDF adapter becomes ready, so the requested jump is lost. Make session disposal safe during effect replay while retaining cleanup on real unmount, and add a StrictMode cold-handoff regression test.
Map raw Markdown search occurrences to rendered positions
Canvas content search counts occurrences in raw Markdown (canvas-search.ts:201–203), but this handoff counts rendered text nodes. For [needle](https://example.test/needle) followed by a paragraph containing needle, selecting the URL occurrence jumps to the paragraph and consumes the request; selecting the paragraph's source occurrence times out. fallbackToFirst: false cannot prevent shifted ordinals. Map the selected source occurrence to a rendered position, or navigate in source mode; report unavailable when it cannot be mapped. Add a Markdown-link regression case.
Scroll matched ranges into view, not entire parent elements
apps/web/src/hooks/searchDom.ts:175
The new clipping check detects an offscreen occurrence, but the scroll below centers its entire parent element rather than the matched Range. If a paragraph or prompt is taller than the panel, an occurrence near its end can remain offscreen after scrolling. The scheduler nevertheless reports success and consumes the navigation request. Position the Range within the scrollable ancestors using its coordinates, and add coverage for distant occurrences in one tall text element.
This addresses the underlying contracts across the search chain, rather than only the individual review examples:
Query identity: preserve raw input, but share the trimmed query between requests, highlights and navigation. Invalidate old rows synchronously before debounce, including filter changes.
Lifecycle ownership: register individual renderer owners. StrictMode replay preserves cold handoffs; disposal of an older same-node renderer cannot erase a newer request. Actual final disposal still closes abandoned sessions.
Source addressing: retain raw-source results and their ordering, but navigate with a source SHA-256 identity and original UTF-16 span. Reuse the shared Markdown parser and Milkdown/ProseMirror model to map the exact visible position, without changing editor selection. Conversation source positions never fall through to an unrelated later user message. The same literal matcher handles server, DOM and PDF offsets, including Unicode case-fold expansion.
Actual visibility: scroll the selected Range through nested clipping/scrolling ancestors and acknowledge only a laid-out, visible target. PDF adapters wait for text-layer completion and map indexed positions across spans; explicit PDF whitespace is not doubled. Selected highlights refresh after text-layer replacement and remain bounded.
Validation
Frontend: 586 tests across 36 files passed.
Server: 39 tests across 2 files passed.
Shared parser/matcher: 41 tests across 2 files passed.
Repository pnpm typecheck, pnpm format, pnpm lint:fix, Web production build, i18n parity and whitespace checks passed.
Real-browser acceptance: cold StrictMode Note and PDF handoffs; padded queries; a 3,070px paragraph with the actual match inside the 300px panel; visible paragraph after a hidden URL; collapsed historical assistant Markdown with code and a later unrelated user match; PDF source-ordinal correction and a phrase spanning multiple PDF text-layer nodes; and Note 501/600 with exactly 500 painted ranges.
Explicit boundaries
Hidden source URLs/syntax remain searchable but report unavailable instead of jumping elsewhere or forcing source mode. Changed or ambiguous sources and unsupported rendering transformations also fail closed with localized feedback. Office reuses the tested Milkdown registration/model path; sandboxed Web/media have no exact-source bridge. Dedicated real-browser Office/Web/media acceptance was not performed in this pass.
The architecture documentation is updated. The new commit is pushed; its remote CI is separate from the previously green head.
The reason will be displayed to describe this comment to others. Learn more.
Original sibling-exclusion problem fixed in 80e81cf. The global getSearchRoots narrowing was removed; isSearchableTextNode scopes document/chrome exclusion only to the closest data-search-renderer. Larger Canvas/conversation roots retain sibling surfaces. Mixed Canvas and conversation regressions re-pass; prior actual Milkdown browser fixture painted all five title/Text/assistant/prompt/quote matches and zero Task List chrome matches. Re-ran these tests on current head; full Web suite is 4,091 passing.
The reason will be displayed to describe this comment to others. Learn more.
Original missing raw-editor bridge fixed in 80e81cf. RawMarkdownEditor now registers unprojected source and the existing indexed adapter, resolves source identity + original offsets, scrolls CodeMirror to render off-viewport content, and acknowledges only the verified visible Range. Re-ran editable/read-only exact raw-URL tests: selection, content and external focus unchanged. Prior real-browser verification: 1,205 lines / only 34 rendered, exact source offset 19,321, 2/3 counter, Range inside the 300px panel. 0dbb244 separately closes cancellation of manual jumps using that async bridge.
The reason will be displayed to describe this comment to others. Learn more.
The original isolated-slice contextual-lowercase problem was fixed in 80e81cf: both matching and resolveSourceRange use whole-source semantics. Reverified ΟΣ / ς -> original span 1–2 and İ ΟΣ / ς -> 3–4, plus the source-resolution regression. The follow-up rejected-boundary stepping example is a distinct real bug, now fixed in 0dbb244: rejected candidates advance one folded unit; accepted candidates keep full-length non-overlap. Added İİi / i\u0307i -> 1–3 and repeated-instance coverage. Full shared suite: 759 passed.
Reuse the canonical match schema across Canvas and Space queries. Preserve sibling search surfaces and stable streamed-result focus. Support raw CodeMirror source navigation, ordinary-HTTP SHA-256, contextual Unicode matching, and terminal PDF failures with registration-owned completion.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addressed the six current review findings plus the confirmed CI contract failure:
CI / Space SEARCH: reuse canvasSearchMatchSchema.strict() instead of maintaining a duplicate response shape. The real RFS route test now asserts source hash, original offset and length, with additional strict-schema rejection coverage.
Mixed search surfaces: document boundaries are local to each renderer. Milkdown chrome stays excluded while sibling titles, Text nodes, user prompts and quoted comments remain searchable.
PDF failure lifecycle: terminal load/source errors settle the handoff with localized feedback; adapter-readiness waits are bounded. Failures belong to their source, and retired adapter registrations cannot process or complete replacement requests. A regression verifies that a late old-source error cannot consume a replacement-source handoff.
Streaming focus: active selection uses the stable row key, not the current array index. Appending results or regrouping fields preserves keyboard ownership; effective query, scope and filter changes reset selection.
Raw Markdown: reuse the existing indexed preview-adapter contract with CodeMirror document offsets and scroll effects. Full-document matching works beyond the rendered viewport, including visible raw link URLs, without editing, changing selection, focusing the editor or switching modes.
Ordinary HTTP: retain Web Crypto when available and lazy-load the declared @noble/hashes SHA-256 fallback otherwise. Fallback digests are checked against Node SHA-256 for empty, ASCII and Unicode input.
Contextual Unicode: validate whole-source lowercase offset boundaries rather than re-lowercasing isolated slices. Both scanning and source resolution cover final Greek sigma and reject partial lowercase expansions.
Validation
Whole-workspace run: 8,001 passing tests, 2 failing tests. Web: 4,084 / 319 files; shared: 757 / 61 files; server: 2,647 passing, plus the two failures below. Other workspace test packages passed.
The original CI failure in rfs.route.test.ts is fixed.
The remaining local failures are the runtime-bundle daemon-help assertion and native filesystem-watcher directory-recreation assertion. Both were reproduced with the same assertions in a temporary pre-refactor aa5a120d checkout using existing dependencies; the watcher is intermittent there. These are not claimed as passing, and no unrelated implementation was changed. The baseline checkout has been removed.
Root format, lint (zero errors; existing warnings remain), and typecheck passed. Latest Web production build, i18n parity, license headers and whitespace checks passed.
Actual component-browser acceptance on authenticated, non-secure HTTP (crypto.subtle absent): CodeMirror navigated to exact original offset 19,321 in a 1,205-line document with only 34 lines rendered, showing 2/3, unchanged selection at 0, and the Range inside the 300px panel. Mixed real Milkdown/sibling surfaces produced all 5 expected highlights and no menu match. A real invalid PDF settled at 0/0 with the localized notification. Actual Virtuoso field regrouping moved the selected row 4 → 5 while preserving the same focused DOM element.
Architecture docs and focused regressions are updated. Temporary fixtures, HTTP credentials/header overrides and servers are cleaned up; the existing user dev service is preserved. The pushed commit needs its own remote CI result; prior green checks are not evidence for this head.
Advance matcher by one unit after rejected boundary
packages/shared/src/utils/text-search.ts:35
A rejected case-fold boundary still advances by the full query length, skipping a later valid match. For example, literalTextMatches('İİi', 'i\u0307i') returns no results, although the second İ plus i matches at source offsets 1–3. This affects every scanner using the shared matcher. Advance by one folded UTF-16 unit after rejecting a candidate; keep the full-length advance for accepted, non-overlapping matches. Add this example to the matcher tests.
Own manual adapter cancellation across replacement, session changes and disposal, and reject stale completions. Advance rejected Unicode expansion candidates one folded unit without changing accepted-match non-overlap.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Manual Next/Previous now owns adapter cancellation, cancels old work on replacement, query/session/owner changes, adapter changes and disposal, and ignores late callbacks unless the navigation identity, active query/session sequence, renderer owner and adapter registration still match. Seven delayed-completion regressions cover query changes, close, same-query reopen, new handoffs, adapter replacement, unmount and a superseding jump.
The shared matcher advances only one folded UTF-16 unit after rejecting an expansion-boundary candidate. Accepted matches remain non-overlapping. The review example İİi / i\u0307i now yields source span 1–3; repeated valid instances are also covered.
Rechecked the three retained original findings (contextual lowercase, raw Markdown bridge, and mixed sibling search). Their existing fixes and tests remain valid; evidence is now attached directly to each review thread rather than only the PR summary.
Validation on this change: 4,091 Web tests / 319 files, 759 shared tests / 61 files, and 96 server search/RFS tests / 2 files, all passing. Root format, lint (zero errors; existing warnings), typecheck, Web production build, i18n, headers and diff checks passed. No new dependencies. Architecture documentation updated.
This pass did not repeat real-browser acceptance or the full unrelated server suites; previous real-browser results are explicitly identified as prior evidence in the thread replies. New-head remote CI must still finish before claiming a green handoff.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Cross-renderer focus ownership and asynchronous history navigation warrant final human review, particularly without real-backend end-to-end validation.
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
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.
Summary
Validation
pnpm typecheckpnpm format(no additional changes)pnpm lint:fix(0 errors; existing warnings remain; no additional changes)pnpm check:i18npnpm --filter @huabu/web buildgit diff --checkScope and limitations