Skip to content

New UI components for the S3 explorer with Codex#1058

Merged
garronej merged 56 commits intoexplorer_deep_reworkfrom
s3-ui-codex
Mar 4, 2026
Merged

New UI components for the S3 explorer with Codex#1058
garronej merged 56 commits intoexplorer_deep_reworkfrom
s3-ui-codex

Conversation

@marchufschmitt
Copy link
Contributor

@marchufschmitt marchufschmitt commented Mar 2, 2026

Summary by CodeRabbit

  • New Features

    • Added a rich S3-style breadcrumb bar and path control with edit/validate modes, hinting, bookmarking, and profile selector.
    • New bookmarking UX: add/edit name modal, bookmark chips, pinned‑chips bar, entry points list, and bucket-type indicators.
    • Utilities for robust S3 URI parsing/validation exported for use across the UI.
  • Documentation

    • Extensive Storybook coverage demonstrating states, interactions, validation flows, and edge cases.
  • Chores

    • Added IDE run configuration and a few UI scaffolding files.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a large set of UI features: S3 path and URI bars with edit/validation flows, bookmarks UI (chips, rows, pinned bar, modals, lists), an S3 profile selector, S3 URI utilities/types, Storybook stories for many components, a small placeholder navigation file, and a VSCode MCP config.

Changes

Cohort / File(s) Summary
VSCode MCP Configuration
\.vscode/mcp.json
New VSCode MCP config defining an HTTP server figma at http://127.0.0.1:3845/mcp with empty inputs.
S3 Path Control & Bar
web/src/ui/shared/S3PathControl.tsx, web/src/ui/shared/S3PathControl.stories.tsx, web/src/ui/shared/S3PathControl.atoms.stories.tsx, web/src/ui/shared/S3UriBar.tsx, web/src/ui/shared/S3UriBar.stories.tsx, web/src/ui/shared/S3UriBar.spec.md
Adds full S3 path and URI bar components with read/edit modes, breadcrumbs, collapsing/ellipsis logic, validation/edit workflows, hints/autocomplete, bookmarking, extensive styles, and numerous Storybook stories and specs.
S3 URI Utilities
web/src/core/tools/S3Uri.ts
New S3Uri typed model, parsers, stringify/get-key helpers, containment checks, and Zod validators for runtime validation.
Bookmarks UI (components & stories)
web/src/ui/shared/bookmarks/BookmarkChip.tsx, web/src/ui/shared/bookmarks/BookmarkChip.stories.tsx, web/src/ui/shared/bookmarks/BookmarkNameModal.tsx, web/src/ui/shared/bookmarks/BookmarkPinButton.tsx, web/src/ui/shared/bookmarks/BookmarkRowItem.tsx, web/src/ui/shared/bookmarks/BookmarksOverview.stories.tsx, web/src/ui/shared/bookmarks/EntryPointsList.tsx, web/src/ui/shared/bookmarks/EntryPointsList.stories.tsx, web/src/ui/shared/bookmarks/PinnedChipsBar.tsx, web/src/ui/shared/bookmarks/BucketTypeChip.tsx, web/src/ui/shared/bookmarks/BucketTypeChip.stories.tsx
Introduces bookmark feature components (chip, row item, pin button, name modal, bucket-type chip, pinned chips bar, entry-points list), utility stories, and an overview story wiring bookmarking flows with S3PathControl.
Bookmarks utilities & types
web/src/ui/shared/bookmarks/getDefaultBookmarkLabelFromPath.ts, web/src/ui/shared/bookmarks/types.ts
Adds getDefaultBookmarkLabelFromPath utility and bookmark-related TypeScript types (Bookmark, BucketType, BucketEntry, CreateBookmarkInput).
S3 Profile Selector
web/src/ui/shared/s3-profile-selector/S3ProfileSelector.tsx, web/src/ui/shared/s3-profile-selector/S3ProfileSelector.stories.tsx, web/src/ui/shared/s3-profile-selector/types.ts, web/src/ui/shared/s3-profile-selector/index.ts
Adds S3ProfileSelector component with search/keyboard/accessibility, profile types/statuses, stories, and a barrel index re-exporting selector and types.
Placeholder Navigation
web/src/ui/shared/S3ExplorerNavigationBar.tsx
Adds a placeholder navigation-bar file containing TODOs and no functional logic.
General UI guidance
web/src/ui/general.spec.md
Adds tss-react styling guidelines and recommended patterns.
Stories & atoms
web/src/ui/shared/S3PathControl.atoms.stories.tsx, web/src/ui/shared/S3PathControl.stories.tsx, various .stories.tsx files under bookmarks and s3-profile-selector
Multiple Storybook story files added/expanded to demonstrate components, interactive play handlers, and visual states.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as S3PathControl / S3UriBar
    participant Validator as validatePath (async)
    participant Host as Host App (callbacks)

    rect rgba(200,220,255,0.5)
    User->>UI: enter edit / click validate / select hint
    UI->>Validator: validatePath(draft) / parse prefix / request hints
    Validator-->>UI: validation result or hints
    UI->>Host: onNavigate / onS3UriPrefixChange / onIsEditingChange
    Host-->>UI: state update / navigation confirmation
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 I nibble breadcrumbs, hop and fix,
Chips, pins, and profiles in my mix,
URIs parsed, hints in view,
Stories sparkle — hop, review! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective: introducing new UI components for S3 explorer functionality. It directly relates to the changeset's primary focus on multiple new S3-related components and utilities.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch s3-ui-codex

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
.vscode/mcp.json (1)

2-8: Consider making this VSCode MCP config opt-in for contributors.

This hardcodes a local endpoint (127.0.0.1:3845) in a repo-tracked .vscode file, which can be noisy/non-functional for teammates who don’t run that local MCP server. Prefer documenting it in README (or committing a sample file) so local workspace setup remains explicit and portable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.vscode/mcp.json around lines 2 - 8, The .vscode/mcp.json currently
hardcodes a local MCP endpoint under the "servers" -> "figma" -> "url" key which
can be noisy for contributors; change this to an opt-in setup by removing or
replacing the concrete URL in .vscode/mcp.json and instead commit a template
(e.g., .vscode/mcp.json.sample) with a placeholder for "url" and add the real
.vscode/mcp.json to .gitignore, and update README to document how to
enable/configure the "figma" MCP server and the expected "servers" / "inputs"
keys so contributors can opt-in safely.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.vscode/mcp.json:
- Around line 2-8: The .vscode/mcp.json currently hardcodes a local MCP endpoint
under the "servers" -> "figma" -> "url" key which can be noisy for contributors;
change this to an opt-in setup by removing or replacing the concrete URL in
.vscode/mcp.json and instead commit a template (e.g., .vscode/mcp.json.sample)
with a placeholder for "url" and add the real .vscode/mcp.json to .gitignore,
and update README to document how to enable/configure the "figma" MCP server and
the expected "servers" / "inputs" keys so contributors can opt-in safely.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a96798 and c5cb2d5.

📒 Files selected for processing (1)
  • .vscode/mcp.json

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/ui/shared/S3PathControl.atoms.stories.tsx`:
- Around line 14-17: The BackButton story is failing TS2322 because
StoryObj<typeof meta> derives props from meta.component (S3PathBackButton) which
requires classes, cx, canGoBack, and onBack but the story's args only provides
canGoBack; fix by giving the story a custom args type that relaxes or narrows
the component props (for example use StoryObj<React.ComponentProps<typeof
S3PathBackButton>> with Partial or pick only canGoBack) so the args: {
canGoBack: true } type-checks; update the BackButton story declaration to use
that custom StoryObj type (referencing meta, S3PathBackButton, and the
BackButton story name) and/or explicitly type args as Partial<...> to satisfy
strict TypeScript.

In `@web/src/ui/shared/S3PathControl.tsx`:
- Around line 1081-1088: The current copy logic calls
setInfoMessage(copyMessage) and onCopy(value) regardless of whether
navigator.clipboard?.writeText(value) succeeds; change the flow in S3PathControl
(the block using navigator.clipboard?.writeText, setInfoMessage and onCopy) so
that setInfoMessage(copyMessage) and onCopy?.(value) are executed only inside
the try after await navigator.clipboard?.writeText(value) completes
successfully, and keep the catch block for silently ignoring or optionally
setting an error message; this ensures "Path copied." is only shown when the
clipboard write actually succeeds.
- Line 1067: The forEach callback on tailIndices is using a single-expression
arrow which implicitly returns the result of collapsed.push(crumbs[index]) and
triggers the lint rule; change the callback to a block-bodied arrow (e.g.,
tailIndices.forEach(index => { collapsed.push(crumbs[index]); });) so the push
call is executed without an implicit return. Ensure you update the invocation
where tailIndices, collapsed, and crumbs are used in S3PathControl to use the
braced callback form.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5cb2d5 and d3728a0.

📒 Files selected for processing (4)
  • web/src/ui/shared/S3ExplorerNavigationBar.tsx
  • web/src/ui/shared/S3PathControl.atoms.stories.tsx
  • web/src/ui/shared/S3PathControl.stories.tsx
  • web/src/ui/shared/S3PathControl.tsx
✅ Files skipped from review due to trivial changes (1)
  • web/src/ui/shared/S3ExplorerNavigationBar.tsx

Comment on lines +14 to +17
const meta = {
title: "Shared/S3PathControl/Atoms",
component: S3PathBackButton
} satisfies Meta<typeof S3PathBackButton>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "S3PathControl.atoms.stories.tsx" | head -20

Repository: InseeFrLab/onyxia

Length of output: 112


🏁 Script executed:

cat -n ./web/src/ui/shared/S3PathControl.atoms.stories.tsx

Repository: InseeFrLab/onyxia

Length of output: 9853


🏁 Script executed:

find . -type f -name "S3PathControl.ts*" -o -name "S3PathControl.tsx" | grep -v stories

Repository: InseeFrLab/onyxia

Length of output: 98


🏁 Script executed:

cat -n ./web/src/ui/shared/S3PathControl.tsx

Repository: InseeFrLab/onyxia

Length of output: 50373


🏁 Script executed:

find . -name "tsconfig*" -type f | head -5

Repository: InseeFrLab/onyxia

Length of output: 133


🏁 Script executed:

cat ./web/tsconfig.json | head -50

Repository: InseeFrLab/onyxia

Length of output: 821


🏁 Script executed:

rg "TS2322" . --type-list 2>/dev/null | head -20

Repository: InseeFrLab/onyxia

Length of output: 43


🏁 Script executed:

head -100 ./web/src/ui/shared/S3PathControl.atoms.stories.tsx | tail -60

Repository: InseeFrLab/onyxia

Length of output: 1503


🏁 Script executed:

find . -type f -name "*.log" -o -name ".github" -type d | grep -E "(workflow|action|log)" | head -20

Repository: InseeFrLab/onyxia

Length of output: 43


🏁 Script executed:

find .github -type f -name "*.yml" -o -name "*.yaml" 2>/dev/null | head -10

Repository: InseeFrLab/onyxia

Length of output: 133


🏁 Script executed:

cat .github/workflows/ci.yaml | grep -A 20 -B 5 "typescript\|tsc\|type"

Repository: InseeFrLab/onyxia

Length of output: 1161


🏁 Script executed:

cat .github/workflows/ci.yaml | head -150

Repository: InseeFrLab/onyxia

Length of output: 4742


🏁 Script executed:

cat ./web/package.json | grep -A 5 '"build"'

Repository: InseeFrLab/onyxia

Length of output: 414


🏁 Script executed:

cd ./web && yarn tsc --noEmit 2>&1 | grep -A 5 "S3PathControl.atoms.stories"

Repository: InseeFrLab/onyxia

Length of output: 43


🏁 Script executed:

cd ./web && yarn tsc --noEmit 2>&1 | head -100

Repository: InseeFrLab/onyxia

Length of output: 491


🏁 Script executed:

find ./web/src -name "*.stories.tsx" -o -name "*.stories.ts" | head -5

Repository: InseeFrLab/onyxia

Length of output: 350


Fix TS2322 in BackButton story typing (blocks build with strict TypeScript).

BackButton uses StoryObj<typeof meta> where meta.component is S3PathBackButton, which requires props classes, cx, canGoBack, and onBack. The args: { canGoBack: true } at line 32 omits required props, causing a type mismatch despite the render function providing them. Storybook enforces args type compatibility at compilation time.

Suggested fix (custom args type for this story)
 type Story = StoryObj<typeof meta>;
+type BackButtonStoryArgs = { canGoBack: boolean };
+type BackButtonStory = StoryObj<BackButtonStoryArgs>;
 
-export const BackButton: Story = {
+export const BackButton: BackButtonStory = {
     args: {
         canGoBack: true
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/ui/shared/S3PathControl.atoms.stories.tsx` around lines 14 - 17, The
BackButton story is failing TS2322 because StoryObj<typeof meta> derives props
from meta.component (S3PathBackButton) which requires classes, cx, canGoBack,
and onBack but the story's args only provides canGoBack; fix by giving the story
a custom args type that relaxes or narrows the component props (for example use
StoryObj<React.ComponentProps<typeof S3PathBackButton>> with Partial or pick
only canGoBack) so the args: { canGoBack: true } type-checks; update the
BackButton story declaration to use that custom StoryObj type (referencing meta,
S3PathBackButton, and the BackButton story name) and/or explicitly type args as
Partial<...> to satisfy strict TypeScript.

collapsed.push(ellipsisCrumb);
}

tailIndices.forEach(index => collapsed.push(crumbs[index]));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "S3PathControl.tsx" -type f

Repository: InseeFrLab/onyxia

Length of output: 98


🏁 Script executed:

head -1070 web/src/ui/shared/S3PathControl.tsx | tail -10

Repository: InseeFrLab/onyxia

Length of output: 352


🏁 Script executed:

# Get exact content around line 1067
sed -n '1060,1075p' web/src/ui/shared/S3PathControl.tsx

Repository: InseeFrLab/onyxia

Length of output: 521


🏁 Script executed:

# Check if there's a Biome/linter configuration
find . -name "biome.json" -o -name ".biomerc*" -o -name "biome.jsonc" | head -5

Repository: InseeFrLab/onyxia

Length of output: 73


🏁 Script executed:

# Search for similar forEach patterns in the codebase to see if this rule is being enforced
rg "forEach\s*\(\s*\w+\s*=>\s*\w+\." -t tsx -t ts --max-count=10

Repository: InseeFrLab/onyxia

Length of output: 88


🏁 Script executed:

# Check Biome configuration
cat biome.json

Repository: InseeFrLab/onyxia

Length of output: 1835


🏁 Script executed:

# Search for similar forEach patterns without file type restriction
rg "forEach\s*\(\s*\w+\s*=>\s*\w+\." --max-count=10

Repository: InseeFrLab/onyxia

Length of output: 165


🏁 Script executed:

# Verify the exact line number
rg -n "tailIndices.forEach" web/src/ui/shared/S3PathControl.tsx

Repository: InseeFrLab/onyxia

Length of output: 134


🏁 Script executed:

# Get broader context to understand the full function
sed -n '1050,1080p' web/src/ui/shared/S3PathControl.tsx

Repository: InseeFrLab/onyxia

Length of output: 860


Fix Biome lint error: forEach callback should not implicitly return.

At line 1067, collapsed.push(...) is returned implicitly from the arrow callback, which violates lint/suspicious/useIterableCallbackReturn. The single-expression arrow function syntax causes the result of push() to be implicitly returned; this should be wrapped in braces to use block form.

Suggested fix
-        tailIndices.forEach(index => collapsed.push(crumbs[index]));
+        tailIndices.forEach(index => {
+            collapsed.push(crumbs[index]);
+        });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tailIndices.forEach(index => collapsed.push(crumbs[index]));
tailIndices.forEach(index => {
collapsed.push(crumbs[index]);
});
🧰 Tools
🪛 Biome (2.4.4)

[error] 1067-1067: This callback passed to forEach() iterable method should not return a value.

(lint/suspicious/useIterableCallbackReturn)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/ui/shared/S3PathControl.tsx` at line 1067, The forEach callback on
tailIndices is using a single-expression arrow which implicitly returns the
result of collapsed.push(crumbs[index]) and triggers the lint rule; change the
callback to a block-bodied arrow (e.g., tailIndices.forEach(index => {
collapsed.push(crumbs[index]); });) so the push call is executed without an
implicit return. Ensure you update the invocation where tailIndices, collapsed,
and crumbs are used in S3PathControl to use the braced callback form.

Comment on lines +1081 to +1088
try {
await navigator.clipboard?.writeText(value);
} catch {
// Ignore clipboard errors.
}

setInfoMessage(copyMessage);
onCopy?.(value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Only show copy success when clipboard write actually succeeds.

At Lines [1081]-[1088], "Path copied." is shown even when clipboard write fails, which can mislead users.

Suggested fix
-        try {
-            await navigator.clipboard?.writeText(value);
-        } catch {
-            // Ignore clipboard errors.
-        }
-
-        setInfoMessage(copyMessage);
+        let copied = false;
+        try {
+            await navigator.clipboard?.writeText(value);
+            copied = true;
+        } catch {
+            // Ignore clipboard errors.
+        }
+
+        if (copied) {
+            setInfoMessage(copyMessage);
+        }
         onCopy?.(value);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
await navigator.clipboard?.writeText(value);
} catch {
// Ignore clipboard errors.
}
setInfoMessage(copyMessage);
onCopy?.(value);
let copied = false;
try {
await navigator.clipboard?.writeText(value);
copied = true;
} catch {
// Ignore clipboard errors.
}
if (copied) {
setInfoMessage(copyMessage);
}
onCopy?.(value);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/ui/shared/S3PathControl.tsx` around lines 1081 - 1088, The current
copy logic calls setInfoMessage(copyMessage) and onCopy(value) regardless of
whether navigator.clipboard?.writeText(value) succeeds; change the flow in
S3PathControl (the block using navigator.clipboard?.writeText, setInfoMessage
and onCopy) so that setInfoMessage(copyMessage) and onCopy?.(value) are executed
only inside the try after await navigator.clipboard?.writeText(value) completes
successfully, and keep the catch block for silently ignoring or optionally
setting an error message; this ensures "Path copied." is only shown when the
clipboard write actually succeeds.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
web/src/ui/shared/S3UriBar.tsx (4)

502-518: Consider clarifying the object hint behavior.

When selecting an object hint, the code creates a NonTerminatedByDelimiter prefix rather than an S3Uri.Object. This is likely intentional for the navigation/autocomplete UX, but a brief comment would help clarify this design choice for future maintainers.

📝 Suggested comment
         const nextPrefix: S3Uri.Prefix =
             hint.type === "key-segment"
                 ? {
                       type: "prefix",
                       bucket: sourcePrefix.bucket,
                       delimiter: sourcePrefix.delimiter,
                       keySegments: [...sourcePrefix.keySegments, hint.name],
                       isDelimiterTerminated: true
                   }
-                : {
+                : // Object hints are treated as non-terminated prefixes for navigation purposes
+                  {
                       type: "prefix",
                       bucket: sourcePrefix.bucket,
                       delimiter: sourcePrefix.delimiter,
                       keySegments: [...sourcePrefix.keySegments],
                       isDelimiterTerminated: false,
                       nextKeySegmentPrefix: hint.name
                   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/ui/shared/S3UriBar.tsx` around lines 502 - 518, The selection of an
object hint currently constructs a non-terminated prefix (S3Uri.Prefix) with
isDelimiterTerminated:false and nextKeySegmentPrefix set (in the branch where
hint.type !== "key-segment") instead of constructing an S3Uri.Object; add a
concise comment above this conditional (near the S3Uri.Prefix construction)
explaining that this is intentional for navigation/autocomplete UX—we keep a
NonTerminatedByDelimiter-style prefix so the UI can continue showing/expanding
suggestions rather than treating the selection as a final S3Uri.Object;
reference the hint.type check, isDelimiterTerminated flag and
nextKeySegmentPrefix in the comment so future maintainers understand the
decision.

623-628: Consider documenting the 250ms blur timing guard.

The timing check prevents the blur handler from closing edit mode immediately after entering it, which handles edge cases where relatedTarget is null. A brief comment explaining this UX protection would improve maintainability.

📝 Suggested comment
+        // Guard against immediate blur when relatedTarget is null (e.g., touch devices
+        // or certain focus transitions). Allow a brief window after entering edit mode.
         if (
             nextFocusedElement === null &&
             performance.now() - lastEnterEditRequestTimeRef.current < 250
         ) {
             return;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/ui/shared/S3UriBar.tsx` around lines 623 - 628, Add a brief inline
comment explaining the 250ms blur timing guard: in the blur handler inside
S3UriBar, annotate why we check nextFocusedElement === null and
performance.now() - lastEnterEditRequestTimeRef.current < 250 (to avoid
immediately exiting edit mode right after entering it when relatedTarget can be
null), and mention that this protects against focus timing edge-cases/rapid
transitions; place the comment directly above the if that uses
nextFocusedElement, lastEnterEditRequestTimeRef, and performance.now().

262-262: Consider clearing stale measurement refs when crumbs change.

The measureCrumbRefs array grows to accommodate new crumbs but isn't trimmed when the crumb count decreases. While this doesn't cause functional issues (only current indices are read), it could lead to memory overhead with many navigation changes.

♻️ Proposed fix - clear refs when crumbs change

Add a cleanup in the measurement effect or reset the array length:

     useEffect(() => {
+        // Trim stale refs when crumb count decreases
+        measureCrumbRefs.current.length = crumbs.length;
+
         if (isEditing) {
             setDisplayCrumbs(crumbs);
             return;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/ui/shared/S3UriBar.tsx` at line 262, measureCrumbRefs
(useRef<Array<HTMLSpanElement | null>>) currently only grows when new crumbs are
added and never trimmed, which can leak stale span references; update the
measurement effect that runs on crumbs changes (the effect that reads
measureCrumbRefs.current) to trim or reset the ref array to match the current
crumbs length (e.g., set measureCrumbRefs.current.length = crumbs.length or
reassign a new array of the correct length) so entries beyond the current crumb
count are released whenever crumbs change.

436-436: Static analysis: forEach callback should not return a value.

The forEach callback implicitly returns the result of collapsed.push(), which is flagged by Biome. While this doesn't affect runtime behavior, it's a code smell.

♻️ Proposed fix
-        tailIndices.forEach(index => collapsed.push(crumbs[index]));
+        for (const index of tailIndices) {
+            collapsed.push(crumbs[index]);
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/ui/shared/S3UriBar.tsx` at line 436, The Biome warning comes from the
forEach callback on tailIndices implicitly returning the value of
collapsed.push(crumbs[index]); update the callback in S3UriBar (the
tailIndices.forEach usage) to use a block-bodied function or a simple for...of
loop so the callback does not return a value (e.g., replace the
expression-bodied arrow with a block that calls collapsed.push(crumbs[index]) or
iterate with for (const index of tailIndices) { collapsed.push(crumbs[index]);
}).
web/src/core/tools/S3Uri.ts (1)

81-85: Potential issue: match[1] could be undefined for edge case URIs.

The regex /^s3:\/\/([^/]+)(\/?.*)$/ captures the bucket in group 1, but TypeScript doesn't narrow this after the null check on match. If match is not null, match[1] should always exist for this pattern, but adding an explicit check would make the code more defensive.

🛡️ Proposed defensive check
     if (match === null) {
         throw new Error(`Malformed S3 URI: ${value}`);
     }

-    const bucket = match[1];
+    const bucket = match[1];
+
+    if (bucket === undefined || bucket === "") {
+        throw new Error(`Malformed S3 URI (missing bucket): ${value}`);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/core/tools/S3Uri.ts` around lines 81 - 85, The current S3 URI parsing
uses const match = value.match(/^s3:\/\/([^/]+)(\/?.*)$/) and only checks for
match === null; add a defensive check that ensures the bucket capture (match[1])
is defined and non-empty before using it (e.g., if (!match || !match[1]) throw
new Error(`Malformed S3 URI: ${value}`)); update the code paths in the S3Uri
parsing logic that reference match[1] and match[2] (the bucket and key) to rely
on this validated match so TypeScript no longer assumes undefined-safe usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@web/src/core/tools/S3Uri.ts`:
- Around line 81-85: The current S3 URI parsing uses const match =
value.match(/^s3:\/\/([^/]+)(\/?.*)$/) and only checks for match === null; add a
defensive check that ensures the bucket capture (match[1]) is defined and
non-empty before using it (e.g., if (!match || !match[1]) throw new
Error(`Malformed S3 URI: ${value}`)); update the code paths in the S3Uri parsing
logic that reference match[1] and match[2] (the bucket and key) to rely on this
validated match so TypeScript no longer assumes undefined-safe usage.

In `@web/src/ui/shared/S3UriBar.tsx`:
- Around line 502-518: The selection of an object hint currently constructs a
non-terminated prefix (S3Uri.Prefix) with isDelimiterTerminated:false and
nextKeySegmentPrefix set (in the branch where hint.type !== "key-segment")
instead of constructing an S3Uri.Object; add a concise comment above this
conditional (near the S3Uri.Prefix construction) explaining that this is
intentional for navigation/autocomplete UX—we keep a
NonTerminatedByDelimiter-style prefix so the UI can continue showing/expanding
suggestions rather than treating the selection as a final S3Uri.Object;
reference the hint.type check, isDelimiterTerminated flag and
nextKeySegmentPrefix in the comment so future maintainers understand the
decision.
- Around line 623-628: Add a brief inline comment explaining the 250ms blur
timing guard: in the blur handler inside S3UriBar, annotate why we check
nextFocusedElement === null and performance.now() -
lastEnterEditRequestTimeRef.current < 250 (to avoid immediately exiting edit
mode right after entering it when relatedTarget can be null), and mention that
this protects against focus timing edge-cases/rapid transitions; place the
comment directly above the if that uses nextFocusedElement,
lastEnterEditRequestTimeRef, and performance.now().
- Line 262: measureCrumbRefs (useRef<Array<HTMLSpanElement | null>>) currently
only grows when new crumbs are added and never trimmed, which can leak stale
span references; update the measurement effect that runs on crumbs changes (the
effect that reads measureCrumbRefs.current) to trim or reset the ref array to
match the current crumbs length (e.g., set measureCrumbRefs.current.length =
crumbs.length or reassign a new array of the correct length) so entries beyond
the current crumb count are released whenever crumbs change.
- Line 436: The Biome warning comes from the forEach callback on tailIndices
implicitly returning the value of collapsed.push(crumbs[index]); update the
callback in S3UriBar (the tailIndices.forEach usage) to use a block-bodied
function or a simple for...of loop so the callback does not return a value
(e.g., replace the expression-bodied arrow with a block that calls
collapsed.push(crumbs[index]) or iterate with for (const index of tailIndices) {
collapsed.push(crumbs[index]); }).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbb9707 and ff80673.

⛔ Files ignored due to path filters (2)
  • web/src/ui/assets/svg/S3UriBucket.svg is excluded by !**/*.svg
  • web/src/ui/assets/svg/S3UriHome.svg is excluded by !**/*.svg
📒 Files selected for processing (5)
  • web/src/core/tools/S3Uri.ts
  • web/src/ui/general.spec.md
  • web/src/ui/shared/S3UriBar.spec.md
  • web/src/ui/shared/S3UriBar.stories.tsx
  • web/src/ui/shared/S3UriBar.tsx
✅ Files skipped from review due to trivial changes (2)
  • web/src/ui/general.spec.md
  • web/src/ui/shared/S3UriBar.spec.md

@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 4, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@garronej garronej changed the base branch from main to explorer_deep_rework March 4, 2026 13:14
@garronej garronej merged commit 0961af7 into explorer_deep_rework Mar 4, 2026
5 of 6 checks passed
@garronej garronej deleted the s3-ui-codex branch March 4, 2026 13:15
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.

4 participants