feat(web): add oxford views prompt management#11
Conversation
Co-authored-by: napan.vijayvargiya <napan.vijayvargiya@flipkart.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Workflow (#4) * ci: add Docker publish workflow for PRs and main branch Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(docker): pin action versions to full SHAs to fix zizmor alerts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: napan.vijayvargiya <napan.vijayvargiya@flipkart.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Workflow (#5) * ci: add Docker publish workflow for PRs and main branch Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(docker): pin action versions to full SHAs to fix zizmor alerts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(docker): fix invalid secrets expressions causing workflow to be silently skipped Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(docker): remove invalid Docker Hub secrets if-condition Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(docker): fix incorrect action SHAs for buildx and build-push-action Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(docker): use version tags instead of broken SHAs for docker actions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docker): fix cross-compilation of migrate binary for arm64 Removes GOBIN so go install works when cross-compiling. The binary lands in $GOPATH/bin/${GOOS}_${GOARCH}/migrate for cross builds and $GOPATH/bin/migrate for native builds; copy whichever exists to /out/migrate. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docker): fix cross-compilation of migrate binary in devcontainer Dockerfile Same GOBIN cross-compilation fix as web/Dockerfile. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: napan.vijayvargiya <napan.vijayvargiya@flipkart.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> reverted docker files (#7) * reverted docker files * only build amd images --------- Co-authored-by: napan.vijayvargiya <napan.vijayvargiya@flipkart.com> fix line issues Update Dockerfile Docker revert (#8) * ci: add Docker publish workflow for PRs and main branch (#2) Co-authored-by: napan.vijayvargiya <napan.vijayvargiya@flipkart.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Workflow (#4) * ci: add Docker publish workflow for PRs and main branch Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(docker): pin action versions to full SHAs to fix zizmor alerts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: napan.vijayvargiya <napan.vijayvargiya@flipkart.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Workflow (#5) * ci: add Docker publish workflow for PRs and main branch Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(docker): pin action versions to full SHAs to fix zizmor alerts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(docker): fix invalid secrets expressions causing workflow to be silently skipped Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(docker): remove invalid Docker Hub secrets if-condition Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(docker): fix incorrect action SHAs for buildx and build-push-action Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(docker): use version tags instead of broken SHAs for docker actions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docker): fix cross-compilation of migrate binary for arm64 Removes GOBIN so go install works when cross-compiling. The binary lands in $GOPATH/bin/${GOOS}_${GOARCH}/migrate for cross builds and $GOPATH/bin/migrate for native builds; copy whichever exists to /out/migrate. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docker): fix cross-compilation of migrate binary in devcontainer Dockerfile Same GOBIN cross-compilation fix as web/Dockerfile. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: napan.vijayvargiya <napan.vijayvargiya@flipkart.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * reverted docker files * only build amd images * reverted .devcontainer dockerfile * merge * Update Dockerfile * Update Dockerfile * Update Dockerfile --------- Co-authored-by: napan.vijayvargiya <napan.vijayvargiya@flipkart.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Kinshuk Bairagi <kingster@users.noreply.github.com>
…r/langfuse into OxfordViewsIntegration # Please enter a commit message to explain why this merge is necessary, # especially if it merges an updated upstream into a topic branch. # # Lines starting with '#' will be ignored, and an empty message aborts # the commit.
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds Oxford Views: DB model + migration, server TRPC router and public API endpoints, Zod validation and creation UI, listing/detail pages and management components (labels, duplicate, delete), sidebar route, and a Docker publish GitHub Actions workflow. ChangesOxford Views Feature
CI/CD Infrastructure
Sequence Diagram(s)sequenceDiagram
participant Client
participant Frontend as NewOxfordViewForm
participant TRPC as oxfordViewRouter
participant DB as Database
Client->>Frontend: submit create form (name, prompt, labels, commitMessage)
Frontend->>TRPC: oxfordViews.create(projectId, payload)
TRPC->>TRPC: validate RBAC, compute final labels/tags, determine version
TRPC->>DB: transactional INSERT new oxford_views, UPDATE prior labels
DB-->>TRPC: transaction result
TRPC-->>Frontend: return created view
Frontend-->>Client: navigate to detail
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add 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. Comment |
|
OX Security reviewed this pull request — nothing to fix.
Branch |
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (2)
.github/workflows/docker-publish.yml (2)
77-77: Confirm the intended behavior for internal PR image publishing.The push condition allows images from internal PRs (same repository) to be published to GHCR, while correctly blocking external fork PRs. This can clutter the registry with ephemeral PR images and may incur storage costs.
Consider whether internal PR images are needed for testing. If not, simplify the condition to:
push: ${{ github.event_name != 'pull_request' }}If internal PR images are required for integration testing or preview environments, consider adding cleanup automation or using a separate registry namespace for PR images.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/docker-publish.yml at line 77, Decide whether internal PR images should be published; if not, simplify the CI condition by replacing the current push expression (push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}) with a stricter check (push: ${{ github.event_name != 'pull_request' }}) to prevent publishing images for any PRs; if internal PR publishing is required, keep the existing expression but add follow-up automation (cleanup or separate GHCR namespace) to avoid registry clutter.
71-84: ⚡ Quick winConsider adding container image security scanning.
The workflow builds and pushes Docker images without security scanning. Adding a scanning step (e.g., Trivy, Snyk) before push can catch vulnerabilities in dependencies and base images.
🛡️ Example security scanning step
Add this step after the build-and-push step:
- name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@0.30.0 with: image-ref: ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:sha-${{ env.NEXT_PUBLIC_BUILD_ID }} format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' - name: Upload Trivy results to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: 'trivy-results.sarif'Note: This requires the image to be pulled after push, or alternatively scan the local build cache before push using
--inputinstead ofimage-ref.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/docker-publish.yml around lines 71 - 84, Add a container security scan step immediately after the existing "Build and push ${{ matrix.component }}" step: invoke aquasecurity/trivy-action@0.30.0 to scan the built image (image-ref: ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:sha-${{ env.NEXT_PUBLIC_BUILD_ID }}) with format='sarif', output='trivy-results.sarif' and severity='CRITICAL,HIGH', then upload the SARIF with github/codeql-action/upload-sarif@v3 (if: always()) to surface results in the Security tab; alternatively, if you prefer scanning the local build before push, configure the Trivy action to scan the local image/cache via --input instead of image-ref.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/docker-publish.yml:
- Line 72: Replace the floating tag for the Docker build/push action so it's
pinned to a specific commit SHA rather than `@v6`; locate the line using the
action identifier `docker/build-push-action@v6` and update it to the same
SHA-pinning pattern used for other actions (e.g., the pinned SHAs for
checkout/login/metadata) to ensure supply-chain consistency and immutability.
- Around line 39-44: The workflow uses mutable tags for the actions
docker/setup-qemu-action@v3 and docker/setup-buildx-action@v3; update those two
steps to pin the actions to specific commit SHAs (the same pattern used for
checkout, login, and metadata) by replacing the `@v3` tag with the corresponding
full commit SHA for each action so the workflow consistently uses immutable
references.
In `@web/src/features/oxford-views/components/delete-oxford-view-version.tsx`:
- Around line 55-56: The Popover's onOpenChange currently toggles state via
setIsOpen(!isOpen), which can race; change the handler to accept the callback's
open argument and set state directly (e.g., onOpenChange={(open) =>
setIsOpen(open)}), updating the usage in the component where Popover, setIsOpen
and isOpen are defined so the open state is bound from the callback input
instead of inverted.
In `@web/src/features/oxford-views/components/delete-oxford-view.tsx`:
- Line 30: The Popover onOpenChange handler currently toggles local state
(onOpenChange={() => setIsOpen(!isOpen)}) which can desynchronize state; update
the handler to accept the provided open argument and call setIsOpen(open)
instead (i.e., use the onOpenChange open parameter from the Popover component to
set isOpen). Locate the Popover component in delete-oxford-view.tsx and replace
the toggle logic with a handler that uses the open parameter to update isOpen
via setIsOpen(open).
In `@web/src/features/oxford-views/components/oxford-view-detail.tsx`:
- Around line 199-207: The Button currently nests a Link which creates two
interactive elements; change this to render the Link as the Button's child via
the Button's asChild prop: remove the direct interactive nesting by adding
asChild to the Button component and keep the Link as the only interactive
element (preserve
href={`/project/${projectId}/oxford-views/new?promptId=${encodeURIComponent(view.id)}`}
and inner contents including <Plus /> and the span), moving any necessary
styling/className onto the Link so Button props like className don't create a
separate interactive element; update the Button/Link composition around the
Button and Link symbols accordingly.
- Around line 125-137: The memo for pythonCode/jsCode only depends on view?.id
so edits to view.name, view.version or view.labels can leave snippets stale;
update the useMemo dependency list to include view.name, view.version and
view.labels (or a stable serialization of labels) so getPythonCode/getJsCode are
recomputed when those change, and remove the eslint-disable-next-line
react-hooks/exhaustive-deps comment (or adjust it) to reflect the correct
dependencies.
In `@web/src/features/oxford-views/components/oxford-view-history.tsx`:
- Around line 26-38: The useEffect in the OxfordViewHistory component currently
only depends on currentRef.current so it doesn't re-run when the selected
version changes; update the dependency array to include props.currentVersion and
view.version (in addition to currentRef.current) so the effect re-evaluates and
scrolls the newly active item into view, keeping the same scrollIntoView logic
inside the effect.
In `@web/src/features/oxford-views/components/oxford-views-table.tsx`:
- Around line 151-160: The filterOptions query is being called with projectId
possibly equal to an empty string (projectId is set via useProjectIdFromURL() ??
""), and sessionFilterContextId is being set to "" instead of null; guard the
api.oxfordViews.filterOptions.useQuery call so it only runs when projectId is
non-empty (e.g., skip or pass undefined/null until a real id exists) and
normalize sessionFilterContextId to null when projectId is an empty string so
session storage uses the global scope; update references to projectId,
filterOptions.useQuery, useProjectIdFromURL, and sessionFilterContextId
accordingly to prevent querying with "" and to ensure null is used for global
session scoping.
In `@web/src/features/oxford-views/server/routers/oxfordViewRouter.ts`:
- Around line 727-737: The code escapes pathPrefix into escapedPathPrefix and
then uses that in the Prisma startsWith filter, which causes literal-mismatch
for folder names containing SQL wildcard characters; change the logic in the
oxfordView.findMany where clause to use the original pathPrefix (not
escapeSqlLikePattern) for the startsWith comparison (i.e. use pathPrefix ?
`${pathPrefix}/` : undefined or similar) and only apply escapeSqlLikePattern
where a SQL LIKE pattern is actually required elsewhere; update references to
escapedPathPrefix accordingly and keep promptName handling the same.
- Around line 570-586: The code wrongly uses escapeSqlLikePattern() for Prisma
startsWith filters which prevents matching names containing '%' or '_' — replace
escapedTarget and escapedSource usages in the two
ctx.prisma.oxfordView.findFirst and findMany calls with the raw targetPath and
sourcePath (preserving the appended "/" as before, e.g. startsWith:
`${targetPath}/` and startsWith: `${sourcePath}/`) so Prisma's startsWith does
real prefix matching; keep escapeSqlLikePattern only for places that build raw
SQL LIKE clauses.
- Around line 669-693: The filterOptions resolver currently queries project data
without enforcing project RBAC; before running the Promise.all, call the
existing project access guard (throwIfNoProjectAccess) using the current context
and input.projectId to validate the caller has access to that project (e.g.,
await ctx.project.throwIfNoProjectAccess(input.projectId)); then proceed with
the three ctx.prisma queries (oxfordView.groupBy and the two
ctx.prisma.$queryRaw calls) so only authorized callers can enumerate names,
tags, and labels.
---
Nitpick comments:
In @.github/workflows/docker-publish.yml:
- Line 77: Decide whether internal PR images should be published; if not,
simplify the CI condition by replacing the current push expression (push: ${{
github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository }}) with a
stricter check (push: ${{ github.event_name != 'pull_request' }}) to prevent
publishing images for any PRs; if internal PR publishing is required, keep the
existing expression but add follow-up automation (cleanup or separate GHCR
namespace) to avoid registry clutter.
- Around line 71-84: Add a container security scan step immediately after the
existing "Build and push ${{ matrix.component }}" step: invoke
aquasecurity/trivy-action@0.30.0 to scan the built image (image-ref: ghcr.io/${{
github.repository_owner }}/${{ matrix.image_name }}:sha-${{
env.NEXT_PUBLIC_BUILD_ID }}) with format='sarif', output='trivy-results.sarif'
and severity='CRITICAL,HIGH', then upload the SARIF with
github/codeql-action/upload-sarif@v3 (if: always()) to surface results in the
Security tab; alternatively, if you prefer scanning the local build before push,
configure the Trivy action to scan the local image/cache via --input instead of
image-ref.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ddc88987-bff6-45f9-adc4-2175651258a9
📒 Files selected for processing (20)
.github/workflows/docker-publish.ymlpackages/shared/prisma/migrations/20260526000000_add_oxford_views/migration.sqlpackages/shared/prisma/schema.prismaweb/src/components/layouts/routes.tsxweb/src/features/oxford-views/components/NewOxfordViewForm/index.tsxweb/src/features/oxford-views/components/NewOxfordViewForm/validation.tsweb/src/features/oxford-views/components/SetOxfordViewVersionLabels/LabelCommandItem.tsxweb/src/features/oxford-views/components/SetOxfordViewVersionLabels/index.tsxweb/src/features/oxford-views/components/delete-oxford-view-folder.tsxweb/src/features/oxford-views/components/delete-oxford-view-version.tsxweb/src/features/oxford-views/components/delete-oxford-view.tsxweb/src/features/oxford-views/components/duplicate-oxford-view.tsxweb/src/features/oxford-views/components/oxford-view-detail.tsxweb/src/features/oxford-views/components/oxford-view-history.tsxweb/src/features/oxford-views/components/oxford-view-new.tsxweb/src/features/oxford-views/components/oxford-views-table.tsxweb/src/features/oxford-views/server/routers/oxfordViewRouter.tsweb/src/pages/project/[projectId]/oxford-views/[[...folder]].tsxweb/src/pages/project/[projectId]/oxford-views/new.tsxweb/src/server/api/root.ts
| - name: Set up QEMU | ||
| uses: docker/setup-qemu-action@v3 | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Pin QEMU and Buildx action versions to commit SHAs for consistency.
Lines 40 and 43 use version tags (@v3), while other actions in this workflow (checkout at line 35, login at line 46, metadata at line 57) are SHA-pinned. This inconsistency weakens supply chain security, as version tags are mutable and can be updated by action maintainers.
🔒 Suggested fix to pin action versions
- name: Set up QEMU
- uses: docker/setup-qemu-action@v3
+ uses: docker/setup-qemu-action@49b3bc8e1090540494070c6f67e47985cd5a6314 # v3
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
+ uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3📝 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.
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v3 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@49b3bc8e1090540494070c6f67e47985cd5a6314 # v3 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3 | |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/docker-publish.yml around lines 39 - 44, The workflow uses
mutable tags for the actions docker/setup-qemu-action@v3 and
docker/setup-buildx-action@v3; update those two steps to pin the actions to
specific commit SHAs (the same pattern used for checkout, login, and metadata)
by replacing the `@v3` tag with the corresponding full commit SHA for each action
so the workflow consistently uses immutable references.
| type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} | ||
|
|
||
| - name: Build and push ${{ matrix.component }} | ||
| uses: docker/build-push-action@v6 |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Pin build-push-action to commit SHA for consistency.
Line 72 uses a version tag (@v6), while other actions like checkout (line 35), login (line 46), and metadata (line 57) are SHA-pinned. See earlier comment on lines 39-44 for the same supply chain security concern.
🔒 Suggested fix
- name: Build and push ${{ matrix.component }}
- uses: docker/build-push-action@v6
+ uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v6📝 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.
| uses: docker/build-push-action@v6 | |
| uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v6 |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/docker-publish.yml at line 72, Replace the floating tag
for the Docker build/push action so it's pinned to a specific commit SHA rather
than `@v6`; locate the line using the action identifier
`docker/build-push-action@v6` and update it to the same SHA-pinning pattern used
for other actions (e.g., the pinned SHAs for checkout/login/metadata) to ensure
supply-chain consistency and immutability.
| onOpenChange={() => setIsOpen(!isOpen)} | ||
| > |
There was a problem hiding this comment.
Bind Popover open state directly from callback input.
Line 55 should use the open parameter; toggling with !isOpen can flip to the wrong state on repeated callback invocations.
Suggested fix
- onOpenChange={() => setIsOpen(!isOpen)}
+ onOpenChange={setIsOpen}📝 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.
| onOpenChange={() => setIsOpen(!isOpen)} | |
| > | |
| onOpenChange={setIsOpen} | |
| > |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/features/oxford-views/components/delete-oxford-view-version.tsx`
around lines 55 - 56, The Popover's onOpenChange currently toggles state via
setIsOpen(!isOpen), which can race; change the handler to accept the callback's
open argument and set state directly (e.g., onOpenChange={(open) =>
setIsOpen(open)}), updating the usage in the component where Popover, setIsOpen
and isOpen are defined so the open state is bound from the callback input
instead of inverted.
| }); | ||
|
|
||
| return ( | ||
| <Popover open={isOpen} onOpenChange={() => setIsOpen(!isOpen)}> |
There was a problem hiding this comment.
Use the open argument from onOpenChange instead of toggling local state.
Line 30 can desynchronize the popover state when the callback fires multiple times with the same target value.
Suggested fix
- <Popover open={isOpen} onOpenChange={() => setIsOpen(!isOpen)}>
+ <Popover open={isOpen} onOpenChange={setIsOpen}>📝 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.
| <Popover open={isOpen} onOpenChange={() => setIsOpen(!isOpen)}> | |
| <Popover open={isOpen} onOpenChange={setIsOpen}> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/features/oxford-views/components/delete-oxford-view.tsx` at line 30,
The Popover onOpenChange handler currently toggles local state (onOpenChange={()
=> setIsOpen(!isOpen)}) which can desynchronize state; update the handler to
accept the provided open argument and call setIsOpen(open) instead (i.e., use
the onOpenChange open parameter from the Popover component to set isOpen).
Locate the Popover component in delete-oxford-view.tsx and replace the toggle
logic with a handler that uses the open parameter to update isOpen via
setIsOpen(open).
| const { pythonCode, jsCode } = useMemo(() => { | ||
| if (!view?.id) return { pythonCode: null, jsCode: null }; | ||
| const sortedLabels = [...view.labels].sort((a, b) => { | ||
| if (a === PRODUCTION_LABEL) return -1; | ||
| if (b === PRODUCTION_LABEL) return 1; | ||
| return a.localeCompare(b); | ||
| }); | ||
| return { | ||
| pythonCode: getPythonCode(view.name, view.version, sortedLabels), | ||
| jsCode: getJsCode(view.name, view.version, sortedLabels), | ||
| }; | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [view?.id]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the relevant snippet and surrounding lines
FILE="web/src/features/oxford-views/components/oxford-view-detail.tsx"
nl -ba "$FILE" | sed -n '90,170p'
# Find where view is sourced/updated in this component
rg -n "const \\{ ?pythonCode, jsCode \\} = useMemo\\(" "$FILE"
rg -n "view\\?" "$FILE"
rg -n "setView\\b|on.*label|labels" "$FILE"
# Locate helpers used by the memo (getPythonCode/getJsCode) to see inputs/expectations
rg -n "function getPythonCode|getPythonCode\\b|getJsCode\\b" "$FILE" || true
rg -n "getPythonCode\\b|getJsCode\\b" "$FILE"
# Search other components that edit labels for an Oxford view and how they update state
rg -n "oxford-views|OxfordView|view\\.labels|PRODUCTION_LABEL" web/src/features/oxford-views -SRepository: flipkart-incubator/langfuse
Length of output: 114
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="web/src/features/oxford-views/components/oxford-view-detail.tsx"
# Show relevant section around the memo with line numbers
cat -n "$FILE" | sed -n '90,170p'
# Find the memo and its dependency array
rg -n "const \\{ *pythonCode, *jsCode *\\} *= *useMemo\\(" "$FILE"
rg -n "useMemo\\(" "$FILE" || true
rg -n "}, *\\[view\\?\\.id\\]" "$FILE" || true
# Check all references to view labels/name/version in this component
rg -n "view\\?(\\.labels|\\.name|\\.version)" "$FILE" || true
rg -n "view\\.(labels|name|version)" "$FILE" || true
# Look for state updates: setView / onChange handlers that might mutate view labels/version
rg -n "\\bsetView\\b|on.*label|labels.*on|set.*labels|handle.*label|PRODUCTION_LABEL" "$FILE"
# Find where view is fetched/constructed in this component
rg -n "use.*(query|fetch|get)\\b|view\\s*=|const \\s*view\\b" "$FILE" || true
# Locate helper signatures used by the memo
rg -n "getPythonCode\\b|getJsCode\\b" "$FILE"Repository: flipkart-incubator/langfuse
Length of output: 5089
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="web/src/features/oxford-views/components/oxford-view-detail.tsx"
# Show the rest of the component around the tabs/actions and any label editing handlers
cat -n "$FILE" | sed -n '170,330p'
# Search for mutations / refetches / cache updates related to prompt labels or versions
rg -n "promptLabels|labels\\s*=|on.*Label|label.*onChange|update.*label|mutate.*label|refetch|invalidate|oxfordViews|promptVersions" "$FILE"
# Search for API calls that likely update versions/labels
rg -n "api\\.(oxfordViews|prompt).*\\.(update|mutate|create|delete)" "$FILE" || true
# Search for components that handle label editing and their props
rg -n "Label|popover|popover|Use View|New version|version history" "$FILE"Repository: flipkart-incubator/langfuse
Length of output: 10112
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the label editing component that’s passed into oxford-view-detail.tsx
FILE="web/src/features/oxford-views/components/SetOxfordViewVersionLabels.tsx"
test -f "$FILE" && echo "Found: $FILE" || (echo "Missing: $FILE"; exit 1)
cat -n "$FILE" | sed -n '1,260p'
# Find how it updates labels/version and whether it triggers refetch/invalidation
rg -n "invalidate|refetch|mutate|update|setQuery|onSuccess|onError|labels|promptLabels|view\\.|version\\b" "$FILE"Repository: flipkart-incubator/langfuse
Length of output: 152
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the file that exports SetOxfordViewVersionLabels
rg -n "export (function|const|default) SetOxfordViewVersionLabels" web/src/features/oxford-views -S || true
rg -n "SetOxfordViewVersionLabels" web/src/features/oxford-views -S
# Also try filename-based discovery
fd "SetOxfordViewVersionLabels" web/src/features/oxford-views -a || true
fd "*SetOxfordViewVersionLabels*" web/src/features/oxford-views -a || trueRepository: flipkart-incubator/langfuse
Length of output: 1317
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="web/src/features/oxford-views/components/SetOxfordViewVersionLabels/index.tsx"
cat -n "$FILE" | sed -n '1,260p'
rg -n "invalidate|refetch|mutate|onSuccess|onError|setQuery|router|push|replace|setCurrent|currentLabel|label\\b|promptLabels|version\\b" "$FILE"Repository: flipkart-incubator/langfuse
Length of output: 12025
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the Button component file and inspect its rendered element
rg -n "export \\{.*Button\\}" web/src/components/ui/button.tsx web/src/components/ui/button.* web/src/components/ui -S || true
fd "button" web/src/components/ui -a || true
# Try common locations
for f in \
web/src/components/ui/button.ts \
web/src/components/ui/button.tsx \
web/src/components/ui/Button.tsx \
web/src/components/ui/Button.ts \
; do
if [ -f "$f" ]; then
echo "---- $f ----"
cat -n "$f" | head -n 200
fi
done
# Also inspect where next/link is used and what it renders (if this repo wraps it)
rg -n "from ['\"]next/link['\"]" web/src -S | head -n 50Repository: flipkart-incubator/langfuse
Length of output: 8923
Recompute SDK “Use View” snippets when the selected view’s labels/name/version change
The memo only keys off view?.id, so label edits for the currently selected version (the mutation invalidates oxfordViews.allVersions) can update view.labels/view.name/view.version while keeping view.id, leaving pythonCode/jsCode stale.
Suggested fix
const { pythonCode, jsCode } = useMemo(() => {
if (!view?.id) return { pythonCode: null, jsCode: null };
@@
return {
pythonCode: getPythonCode(view.name, view.version, sortedLabels),
jsCode: getJsCode(view.name, view.version, sortedLabels),
};
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [view?.id]);
+ }, [view?.id, view?.name, view?.version, view?.labels]);📝 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.
| const { pythonCode, jsCode } = useMemo(() => { | |
| if (!view?.id) return { pythonCode: null, jsCode: null }; | |
| const sortedLabels = [...view.labels].sort((a, b) => { | |
| if (a === PRODUCTION_LABEL) return -1; | |
| if (b === PRODUCTION_LABEL) return 1; | |
| return a.localeCompare(b); | |
| }); | |
| return { | |
| pythonCode: getPythonCode(view.name, view.version, sortedLabels), | |
| jsCode: getJsCode(view.name, view.version, sortedLabels), | |
| }; | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| }, [view?.id]); | |
| const { pythonCode, jsCode } = useMemo(() => { | |
| if (!view?.id) return { pythonCode: null, jsCode: null }; | |
| const sortedLabels = [...view.labels].sort((a, b) => { | |
| if (a === PRODUCTION_LABEL) return -1; | |
| if (b === PRODUCTION_LABEL) return 1; | |
| return a.localeCompare(b); | |
| }); | |
| return { | |
| pythonCode: getPythonCode(view.name, view.version, sortedLabels), | |
| jsCode: getJsCode(view.name, view.version, sortedLabels), | |
| }; | |
| }, [view?.id, view?.name, view?.version, view?.labels]); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/features/oxford-views/components/oxford-view-detail.tsx` around lines
125 - 137, The memo for pythonCode/jsCode only depends on view?.id so edits to
view.name, view.version or view.labels can leave snippets stale; update the
useMemo dependency list to include view.name, view.version and view.labels (or a
stable serialization of labels) so getPythonCode/getJsCode are recomputed when
those change, and remove the eslint-disable-next-line
react-hooks/exhaustive-deps comment (or adjust it) to reflect the correct
dependencies.
| useEffect(() => { | ||
| if ( | ||
| props.currentVersion && | ||
| currentRef.current && | ||
| props.currentVersion === view.version | ||
| ) { | ||
| currentRef.current.scrollIntoView({ | ||
| behavior: "smooth", | ||
| block: "center", | ||
| }); | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [currentRef.current]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="web/src/features/oxford-views/components/oxford-view-history.tsx"
if [ ! -f "$file" ]; then
echo "Missing file: $file"
exit 1
fi
echo "=== File: $file ==="
# Show around the useEffect with dependency array
rg -n "useEffect\(\s*\(\)\s*=>|useEffect\(" -n "$file"
# Print the specific region near line 26-60 and near the useEffect dependency line
nl -ba "$file" | sed -n '1,140p'
nl -ba "$file" | sed -n '140,260p'Repository: flipkart-incubator/langfuse
Length of output: 235
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="web/src/features/oxford-views/components/oxford-view-history.tsx"
if [ ! -f "$file" ]; then
echo "Missing file: $file"
exit 1
fi
echo "=== useEffect regions ==="
# Show the specific lines around the effect and dependency array
cat -n "$file" | sed -n '1,120p'
echo "=== tail around effect ==="
cat -n "$file" | sed -n '120,240p'
echo "=== extract exact useEffect block ==="
# Print from useEffect line to the dependency array line (best-effort)
start_line=$(rg -n "useEffect\(\s*\(\)\s*=>" "$file" | head -n1 | cut -d: -f1)
echo "Detected useEffect start line: $start_line"
if [ -n "${start_line:-}" ]; then
end_line=$((start_line+60))
cat -n "$file" | sed -n "${start_line},${end_line}p"
fiRepository: flipkart-incubator/langfuse
Length of output: 9176
Fix auto-scroll to rerun when the selected version changes
The effect depends on currentRef.current, which won’t change when props.currentVersion updates, so it may not scroll the newly active item into view. Include props.currentVersion (and view.version) in the dependency list.
Suggested fix
useEffect(() => {
if (
props.currentVersion &&
currentRef.current &&
props.currentVersion === view.version
@@
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [currentRef.current]);
+ }, [props.currentVersion, view.version]);📝 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.
| useEffect(() => { | |
| if ( | |
| props.currentVersion && | |
| currentRef.current && | |
| props.currentVersion === view.version | |
| ) { | |
| currentRef.current.scrollIntoView({ | |
| behavior: "smooth", | |
| block: "center", | |
| }); | |
| } | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| }, [currentRef.current]); | |
| useEffect(() => { | |
| if ( | |
| props.currentVersion && | |
| currentRef.current && | |
| props.currentVersion === view.version | |
| ) { | |
| currentRef.current.scrollIntoView({ | |
| behavior: "smooth", | |
| block: "center", | |
| }); | |
| } | |
| }, [props.currentVersion, view.version]); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/features/oxford-views/components/oxford-view-history.tsx` around
lines 26 - 38, The useEffect in the OxfordViewHistory component currently only
depends on currentRef.current so it doesn't re-run when the selected version
changes; update the dependency array to include props.currentVersion and
view.version (in addition to currentRef.current) so the effect re-evaluates and
scrolls the newly active item into view, keeping the same scrollIntoView logic
inside the effect.
| const filterOptions = api.oxfordViews.filterOptions.useQuery( | ||
| { projectId }, | ||
| { | ||
| trpc: { context: { skipBatch: true } }, | ||
| refetchOnMount: false, | ||
| refetchOnWindowFocus: false, | ||
| refetchOnReconnect: false, | ||
| staleTime: Infinity, | ||
| }, | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== filterOptions definition and projectId validation =="
rg -n -C4 'filterOptions|projectId' web/src/features/oxford-views/server/routers/oxfordViewRouter.ts
echo
echo "== caller usage in oxford-views-table =="
rg -n -C4 'api\.oxfordViews\.filterOptions\.useQuery|enabled:|sessionFilterContextId' web/src/features/oxford-views/components/oxford-views-table.tsxRepository: flipkart-incubator/langfuse
Length of output: 18515
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== oxford-views-table.tsx: projectId initialization and surrounding lines =="
sed -n '120,210p' web/src/features/oxford-views/components/oxford-views-table.tsx
echo
echo "== locate useSidebarFilterState implementation =="
rg -n "function useSidebarFilterState|useSidebarFilterState\\(" web/src -S
# Try to show the most relevant implementation file
impl_file="$(rg -n "useSidebarFilterState\\(" web/src -S --files-with-matches | head -n 1 || true)"
if [[ -n "${impl_file:-}" ]]; then
echo
echo "== candidate implementation file: $impl_file =="
wc -l "$impl_file"
sed -n '1,220p' "$impl_file"
fiRepository: flipkart-incubator/langfuse
Length of output: 11842
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== useSidebarFilterState.tsx: sessionFilterContextId usage =="
rg -n "sessionFilterContextId" web/src/features/filters/hooks/useSidebarFilterState.tsx
sed -n '380,520p' web/src/features/filters/hooks/useSidebarFilterState.tsx
echo
echo "== Also show any helpers building sessionStorage keys =="
rg -n "sessionStorage|sessionFilter|storage.*key|filterContextId" web/src/features/filters/hooks/useSidebarFilterState.tsx
rg -n "sessionFilterContextId" web/src/features/filters -S
echo
echo "== useProjectIdFromURL initial value behavior =="
rg -n "function useProjectIdFromURL|export default function useProjectIdFromURL" web/src -S
proj_hook="$(rg -n "useProjectIdFromURL" web/src -S --files-with-matches | head -n 5 | sed -n '1,1p')"
echo "candidate hook file: $proj_hook"
if [[ -n "$proj_hook" ]]; then
sed -n '1,220p' "$proj_hook"
fi
echo
echo "== throwIfNoProjectAccess behavior for empty projectId =="
rg -n "function throwIfNoProjectAccess|throwIfNoProjectAccess\\(" web/src -S
throw_file="$(rg -n "throwIfNoProjectAccess\\(" web/src -S --files-with-matches | head -n 1)"
echo "candidate throwIfNoProjectAccess file: $throw_file"
if [[ -n "$throw_file" ]]; then
rg -n "throwIfNoProjectAccess" "$throw_file"
sed -n '1,240p' "$throw_file"
fiRepository: flipkart-incubator/langfuse
Length of output: 38892
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== useProjectIdFromURL.ts =="
sed -n '1,200p' web/src/hooks/useProjectIdFromURL.ts
echo
echo "== buildSidebarFilterQueryStorageKey definition + createPersistedSidebarFilterQueryState =="
rg -n "buildSidebarFilterQueryStorageKey|createPersistedSidebarFilterQueryState|getPersistedSidebarFilterQueryForContext" web/src/features/filters/hooks/useSidebarFilterState.tsx
sed -n '1,220p' web/src/features/filters/hooks/useSidebarFilterState.tsx
sed -n '220,520p' web/src/features/filters/hooks/useSidebarFilterState.tsx
echo
echo "== Look for effects that synchronize memoryFilterState when context changes =="
rg -n "useEffect\\(|storedFiltersQuery|normalizedSessionFilterContextId|FILTER_QUERY_SESSION_STORAGE_KEY|setMemoryFilterState" web/src/features/filters/hooks/useSidebarFilterState.tsx
# Show likely sections around storedFiltersQuery / memoryFilterState updates
sed -n '520,820p' web/src/features/filters/hooks/useSidebarFilterState.tsx
echo
echo "== Relevant tests for sidebar filter session persistence =="
sed -n '1,220p' web/src/features/filters/sidebarFilterSessionPersistence.clienttest.tsx
echo
echo "== Find how other tables pass sessionFilterContextId =="
rg -n "sessionFilterContextId:\\s*projectId" web/src/features web/src/components -SRepository: flipkart-incubator/langfuse
Length of output: 35250
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== useProjectIdFromURL.ts =="
sed -n '1,200p' web/src/hooks/useProjectIdFromURL.ts
echo
echo "== buildSidebarFilterQueryStorageKey definition + createPersistedSidebarFilterQueryState =="
rg -n "buildSidebarFilterQueryStorageKey|createPersistedSidebarFilterQueryState|getPersistedSidebarFilterQueryForContext" web/src/features/filters/hooks/useSidebarFilterState.tsx
sed -n '1,220p' web/src/features/filters/hooks/useSidebarFilterState.tsx
sed -n '220,520p' web/src/features/filters/hooks/useSidebarFilterState.tsx
echo
echo "== Look for effects that synchronize memoryFilterState when context changes =="
rg -n "useEffect\\(|storedFiltersQuery|normalizedSessionFilterContextId|FILTER_QUERY_SESSION_STORAGE_KEY|setMemoryFilterState" web/src/features/filters/hooks/useSidebarFilterState.tsx
# Show likely sections around storedFiltersQuery / memoryFilterState updates
sed -n '520,820p' web/src/features/filters/hooks/useSidebarFilterState.tsx
echo
echo "== Relevant tests for sidebar filter session persistence =="
sed -n '1,240p' web/src/features/filters/sidebarFilterSessionPersistence.clienttest.tsx
echo
echo "== Find how other tables pass sessionFilterContextId =="
rg -n "sessionFilterContextId:\\s*projectId" web/src/features web/src/components -SRepository: flipkart-incubator/langfuse
Length of output: 35784
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== useProjectIdFromURL.ts =="
sed -n '1,200p' web/src/hooks/useProjectIdFromURL.ts
echo
echo "== buildSidebarFilterQueryStorageKey / persisted state helpers =="
rg -n "buildSidebarFilterQueryStorageKey|createPersistedSidebarFilterQueryState|getPersistedSidebarFilterQueryForContext" web/src/features/filters/hooks/useSidebarFilterState.tsx
sed -n '1,260p' web/src/features/filters/hooks/useSidebarFilterState.tsx
echo
echo "== Keyed session storage + storedFiltersQuery selection =="
sed -n '260,520p' web/src/features/filters/hooks/useSidebarFilterState.tsx
echo
echo "== Sync logic for memoryFilterState when context changes =="
rg -n "useEffect\\(|storedFiltersQuery|normalizedSessionFilterContextId|setMemoryFilterState" web/src/features/filters/hooks/useSidebarFilterState.tsx
sed -n '520,900p' web/src/features/filters/hooks/useSidebarFilterState.tsx
echo
echo "== Tests that cover sidebarFilterSessionPersistence =="
sed -n '1,260p' web/src/features/filters/sidebarFilterSessionPersistence.clienttest.tsx
echo
echo "== Other call sites for sessionFilterContextId =="
rg -n "sessionFilterContextId:\\s*[^,\\}]*projectId" web/src/features web/src/components -SRepository: flipkart-incubator/langfuse
Length of output: 38893
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== persistedSidebarFilterQuery.ts: storage key behavior for null/empty contextId =="
rg -n "function buildSidebarFilterQueryStorageKey|buildSidebarFilterQueryStorageKey" web/src/features/filters/lib -S
file="$(rg -n "buildSidebarFilterQueryStorageKey\\(" web/src/features/filters/lib -S --files-with-matches | head -n 1)"
echo "file: $file"
sed -n '1,220p' "$file"
echo
echo "== getPersistedSidebarFilterQueryForContext: context comparison logic =="
rg -n "getPersistedSidebarFilterQueryForContext" web/src/features/filters/lib -S
sed -n '220,420p' "$file"Repository: flipkart-incubator/langfuse
Length of output: 2678
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== throwIfNoProjectAccess implementation =="
rg -n "export function throwIfNoProjectAccess|function throwIfNoProjectAccess" web/src/features/rbac/utils -S
file="$(rg -n "export function throwIfNoProjectAccess|function throwIfNoProjectAccess" web/src/features/rbac/utils -S --files-with-matches | head -n 1 || true)"
echo "file: $file"
if [[ -n "$file" ]]; then
sed -n '1,240p' "$file"
fi
echo
echo "== check for any explicit empty-string handling =="
if [[ -n "$file" ]]; then
rg -n "''|empty|length|projectId\\.length|trim\\(" "$file" || true
fiRepository: flipkart-incubator/langfuse
Length of output: 115
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== find throwIfNoProjectAccess definition in web/src =="
rg -n "throwIfNoProjectAccess\\b" web/src -S | head -n 50
echo
echo "== candidate definition files =="
rg -n "export function throwIfNoProjectAccess\\b|function throwIfNoProjectAccess\\b" web/src -S --files-with-matches
echo
file="$(rg -n "export function throwIfNoProjectAccess\\b|function throwIfNoProjectAccess\\b" web/src -S --files-with-matches | head -n 1 || true)"
echo "first match file: $file"
if [[ -n "${file:-}" ]]; then
sed -n '1,240p' "$file"
fiRepository: flipkart-incubator/langfuse
Length of output: 4790
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' web/src/features/rbac/utils/checkProjectAccess.tsRepository: flipkart-incubator/langfuse
Length of output: 2131
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,240p' web/src/features/filters/hooks/useKeyedSessionStorageState.tsRepository: flipkart-incubator/langfuse
Length of output: 2540
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "contextId=\"|contextId: \"|\\\"\\\"" web/src/features/filters/sidebarFilterSessionPersistence.clienttest.tsx web/src/features/filters -SRepository: flipkart-incubator/langfuse
Length of output: 192
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== oxfordViewRouter filterOptions query body (for access checks) =="
sed -n '640,730p' web/src/features/oxford-views/server/routers/oxfordViewRouter.ts
echo
echo "== useKeyedSessionStorageState implementation =="
sed -n '1,240p' web/src/features/filters/hooks/useKeyedSessionStorageState.ts
echo
echo "== any tests using empty-string contextId in sidebar filter persistence =="
rg -n "contextId\\s*[:=]\\s*(\"\"|null|undefined)|\"\"\\s*\\)|contextId\\s*=\\s*\"\"" web/src/features/filters/sidebarFilterSessionPersistence.clienttest.tsx web/src/features/filters -SRepository: flipkart-incubator/langfuse
Length of output: 7949
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== oxfordViewRouter filterOptions query body (for access checks) =="
sed -n '640,730p' web/src/features/oxford-views/server/routers/oxfordViewRouter.ts
echo
echo "== useKeyedSessionStorageState implementation =="
sed -n '1,240p' web/src/features/filters/hooks/useKeyedSessionStorageState.ts
echo
echo "== tests using empty-string contextId in sidebar filter persistence =="
rg -n "contextId\\s*[:=]\\s*(\"\"|null|undefined)|\"\"\\s*\\)|contextId\\s*=\\s*\"\"" web/src/features/filters/sidebarFilterSessionPersistence.clienttest.tsx web/src/features/filters -SRepository: flipkart-incubator/langfuse
Length of output: 7945
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== other usages of filterOptions.useQuery with enabled guard =="
rg -n "filterOptions\\.useQuery\\(" web/src/features -S --glob='*.tsx' --glob='*.ts' | head -n 50
echo
echo "== filterOptions.useQuery blocks that contain enabled: Boolean(projectId) =="
rg -n "filterOptions\\.useQuery\\([\\s\\S]{0,300}?enabled\\s*:\\s*Boolean\\(projectId\\)" web/src/features -S --glob='*.tsx' --glob='*.ts' || true
echo
echo "== filterOptions.useQuery blocks with sessionFilterContextId/projectId empty handling =="
rg -n "filterOptions\\.useQuery\\([\\s\\S]{0,120}projectId" web/src/features -S --glob='*.tsx' | head -n 50Repository: flipkart-incubator/langfuse
Length of output: 1768
Guard filterOptions query + normalize sessionFilterContextId when projectId is empty
oxford-views-table.tsx initializes projectId as useProjectIdFromURL() ?? "" and filterOptions.useQuery is currently unguarded, so it can call the filterOptions tRPC procedure with projectId="" before the URL param is ready (the router accepts empty strings via z.string(), then rejects via access checks). Also, sessionFilterContextId: projectId ?? null preserves "", creating a distinct sessionStorage scope instead of falling back to the intended null/global behavior.
Suggested patch
const filterOptions = api.oxfordViews.filterOptions.useQuery(
{ projectId },
{
+ enabled: Boolean(projectId),
trpc: { context: { skipBatch: true } },
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
staleTime: Infinity,
},
);
@@
const queryFilter = useSidebarFilterState(
@@
{
loading: filterOptions.isPending,
stateLocation: "urlAndSessionStorage",
- sessionFilterContextId: projectId ?? null,
+ sessionFilterContextId: projectId || null,
},
);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/features/oxford-views/components/oxford-views-table.tsx` around lines
151 - 160, The filterOptions query is being called with projectId possibly equal
to an empty string (projectId is set via useProjectIdFromURL() ?? ""), and
sessionFilterContextId is being set to "" instead of null; guard the
api.oxfordViews.filterOptions.useQuery call so it only runs when projectId is
non-empty (e.g., skip or pass undefined/null until a real id exists) and
normalize sessionFilterContextId to null when projectId is an empty string so
session storage uses the global scope; update references to projectId,
filterOptions.useQuery, useProjectIdFromURL, and sessionFilterContextId
accordingly to prevent querying with "" and to ensure null is used for global
session scoping.
| const { projectId, sourcePath, targetPath, isSingleVersion } = input; | ||
| const escapedTarget = escapeSqlLikePattern(targetPath); | ||
| const escapedSource = escapeSqlLikePattern(sourcePath); | ||
|
|
||
| const existingTarget = await ctx.prisma.oxfordView.findFirst({ | ||
| where: { projectId, name: { startsWith: `${escapedTarget}/` } }, | ||
| }); | ||
| if (existingTarget) { | ||
| throw new TRPCError({ | ||
| code: "BAD_REQUEST", | ||
| message: `Oxford Views already exist under "${targetPath}/".`, | ||
| }); | ||
| } | ||
|
|
||
| const sourceViews = await ctx.prisma.oxfordView.findMany({ | ||
| where: { projectId, name: { startsWith: `${escapedSource}/` } }, | ||
| orderBy: [{ name: "asc" }, { version: "asc" }], |
There was a problem hiding this comment.
Use raw folder paths for Prisma startsWith filters.
escapeSqlLikePattern() is only correct for raw LIKE clauses. Using the escaped value in startsWith makes names containing % or _ stop matching, so duplicate-folder can falsely report missing source folders or clear target paths.
Suggested fix
const { projectId, sourcePath, targetPath, isSingleVersion } = input;
- const escapedTarget = escapeSqlLikePattern(targetPath);
- const escapedSource = escapeSqlLikePattern(sourcePath);
const existingTarget = await ctx.prisma.oxfordView.findFirst({
- where: { projectId, name: { startsWith: `${escapedTarget}/` } },
+ where: { projectId, name: { startsWith: `${targetPath}/` } },
});
@@
const sourceViews = await ctx.prisma.oxfordView.findMany({
- where: { projectId, name: { startsWith: `${escapedSource}/` } },
+ where: { projectId, name: { startsWith: `${sourcePath}/` } },
orderBy: [{ name: "asc" }, { version: "asc" }],
});📝 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.
| const { projectId, sourcePath, targetPath, isSingleVersion } = input; | |
| const escapedTarget = escapeSqlLikePattern(targetPath); | |
| const escapedSource = escapeSqlLikePattern(sourcePath); | |
| const existingTarget = await ctx.prisma.oxfordView.findFirst({ | |
| where: { projectId, name: { startsWith: `${escapedTarget}/` } }, | |
| }); | |
| if (existingTarget) { | |
| throw new TRPCError({ | |
| code: "BAD_REQUEST", | |
| message: `Oxford Views already exist under "${targetPath}/".`, | |
| }); | |
| } | |
| const sourceViews = await ctx.prisma.oxfordView.findMany({ | |
| where: { projectId, name: { startsWith: `${escapedSource}/` } }, | |
| orderBy: [{ name: "asc" }, { version: "asc" }], | |
| const { projectId, sourcePath, targetPath, isSingleVersion } = input; | |
| const existingTarget = await ctx.prisma.oxfordView.findFirst({ | |
| where: { projectId, name: { startsWith: `${targetPath}/` } }, | |
| }); | |
| if (existingTarget) { | |
| throw new TRPCError({ | |
| code: "BAD_REQUEST", | |
| message: `Oxford Views already exist under "${targetPath}/".`, | |
| }); | |
| } | |
| const sourceViews = await ctx.prisma.oxfordView.findMany({ | |
| where: { projectId, name: { startsWith: `${sourcePath}/` } }, | |
| orderBy: [{ name: "asc" }, { version: "asc" }], |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/features/oxford-views/server/routers/oxfordViewRouter.ts` around
lines 570 - 586, The code wrongly uses escapeSqlLikePattern() for Prisma
startsWith filters which prevents matching names containing '%' or '_' — replace
escapedTarget and escapedSource usages in the two
ctx.prisma.oxfordView.findFirst and findMany calls with the raw targetPath and
sourcePath (preserving the appended "/" as before, e.g. startsWith:
`${targetPath}/` and startsWith: `${sourcePath}/`) so Prisma's startsWith does
real prefix matching; keep escapeSqlLikePattern only for places that build raw
SQL LIKE clauses.
| filterOptions: protectedProjectProcedure | ||
| .input(z.object({ projectId: z.string() })) | ||
| .query(async ({ input, ctx }) => { | ||
| const [names, tags, labels] = await Promise.all([ | ||
| ctx.prisma.oxfordView.groupBy({ | ||
| where: { projectId: input.projectId }, | ||
| by: ["name"], | ||
| take: 1000, | ||
| orderBy: { name: "asc" }, | ||
| }), | ||
| ctx.prisma.$queryRaw<{ value: string }[]>` | ||
| SELECT tags.tag as value | ||
| FROM oxford_views, UNNEST(oxford_views.tags) AS tags(tag) | ||
| WHERE oxford_views.project_id = ${input.projectId} | ||
| GROUP BY tags.tag | ||
| ORDER BY tags.tag ASC; | ||
| `, | ||
| ctx.prisma.$queryRaw<{ value: string }[]>` | ||
| SELECT labels.label as value | ||
| FROM oxford_views, UNNEST(oxford_views.labels) AS labels(label) | ||
| WHERE oxford_views.project_id = ${input.projectId} | ||
| GROUP BY labels.label | ||
| ORDER BY labels.label ASC; | ||
| `, | ||
| ]); |
There was a problem hiding this comment.
Enforce project RBAC in filterOptions.
This query skips throwIfNoProjectAccess, so any authenticated caller can enumerate names, tags, and labels for an arbitrary projectId.
Suggested fix
filterOptions: protectedProjectProcedure
.input(z.object({ projectId: z.string() }))
.query(async ({ input, ctx }) => {
+ throwIfNoProjectAccess({
+ session: ctx.session,
+ projectId: input.projectId,
+ scope: "prompts:read",
+ });
+
const [names, tags, labels] = await Promise.all([
ctx.prisma.oxfordView.groupBy({📝 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.
| filterOptions: protectedProjectProcedure | |
| .input(z.object({ projectId: z.string() })) | |
| .query(async ({ input, ctx }) => { | |
| const [names, tags, labels] = await Promise.all([ | |
| ctx.prisma.oxfordView.groupBy({ | |
| where: { projectId: input.projectId }, | |
| by: ["name"], | |
| take: 1000, | |
| orderBy: { name: "asc" }, | |
| }), | |
| ctx.prisma.$queryRaw<{ value: string }[]>` | |
| SELECT tags.tag as value | |
| FROM oxford_views, UNNEST(oxford_views.tags) AS tags(tag) | |
| WHERE oxford_views.project_id = ${input.projectId} | |
| GROUP BY tags.tag | |
| ORDER BY tags.tag ASC; | |
| `, | |
| ctx.prisma.$queryRaw<{ value: string }[]>` | |
| SELECT labels.label as value | |
| FROM oxford_views, UNNEST(oxford_views.labels) AS labels(label) | |
| WHERE oxford_views.project_id = ${input.projectId} | |
| GROUP BY labels.label | |
| ORDER BY labels.label ASC; | |
| `, | |
| ]); | |
| filterOptions: protectedProjectProcedure | |
| .input(z.object({ projectId: z.string() })) | |
| .query(async ({ input, ctx }) => { | |
| throwIfNoProjectAccess({ | |
| session: ctx.session, | |
| projectId: input.projectId, | |
| scope: "prompts:read", | |
| }); | |
| const [names, tags, labels] = await Promise.all([ | |
| ctx.prisma.oxfordView.groupBy({ | |
| where: { projectId: input.projectId }, | |
| by: ["name"], | |
| take: 1000, | |
| orderBy: { name: "asc" }, | |
| }), | |
| ctx.prisma.$queryRaw<{ value: string }[]>` | |
| SELECT tags.tag as value | |
| FROM oxford_views, UNNEST(oxford_views.tags) AS tags(tag) | |
| WHERE oxford_views.project_id = ${input.projectId} | |
| GROUP BY tags.tag | |
| ORDER BY tags.tag ASC; | |
| `, | |
| ctx.prisma.$queryRaw<{ value: string }[]>` | |
| SELECT labels.label as value | |
| FROM oxford_views, UNNEST(oxford_views.labels) AS labels(label) | |
| WHERE oxford_views.project_id = ${input.projectId} | |
| GROUP BY labels.label | |
| ORDER BY labels.label ASC; | |
| `, | |
| ]); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/features/oxford-views/server/routers/oxfordViewRouter.ts` around
lines 669 - 693, The filterOptions resolver currently queries project data
without enforcing project RBAC; before running the Promise.all, call the
existing project access guard (throwIfNoProjectAccess) using the current context
and input.projectId to validate the caller has access to that project (e.g.,
await ctx.project.throwIfNoProjectAccess(input.projectId)); then proceed with
the three ctx.prisma queries (oxfordView.groupBy and the two
ctx.prisma.$queryRaw calls) so only authorized callers can enumerate names,
tags, and labels.
| const escapedPathPrefix = pathPrefix | ||
| ? escapeSqlLikePattern(pathPrefix) | ||
| : undefined; | ||
|
|
||
| const views = await ctx.prisma.oxfordView.findMany({ | ||
| where: { | ||
| projectId, | ||
| name: promptName | ||
| ? promptName | ||
| : { startsWith: `${escapedPathPrefix}/` }, | ||
| }, |
There was a problem hiding this comment.
Don't escape pathPrefix before startsWith.
This has the same literal-mismatch bug as duplicateFolder: a folder name containing % or _ will no longer match the Prisma filter, so deletes can silently miss valid Oxford Views.
Suggested fix
- const escapedPathPrefix = pathPrefix
- ? escapeSqlLikePattern(pathPrefix)
- : undefined;
-
const views = await ctx.prisma.oxfordView.findMany({
where: {
projectId,
name: promptName
? promptName
- : { startsWith: `${escapedPathPrefix}/` },
+ : { startsWith: `${pathPrefix}/` },
},
});📝 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.
| const escapedPathPrefix = pathPrefix | |
| ? escapeSqlLikePattern(pathPrefix) | |
| : undefined; | |
| const views = await ctx.prisma.oxfordView.findMany({ | |
| where: { | |
| projectId, | |
| name: promptName | |
| ? promptName | |
| : { startsWith: `${escapedPathPrefix}/` }, | |
| }, | |
| const views = await ctx.prisma.oxfordView.findMany({ | |
| where: { | |
| projectId, | |
| name: promptName | |
| ? promptName | |
| : { startsWith: `${pathPrefix}/` }, | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/features/oxford-views/server/routers/oxfordViewRouter.ts` around
lines 727 - 737, The code escapes pathPrefix into escapedPathPrefix and then
uses that in the Prisma startsWith filter, which causes literal-mismatch for
folder names containing SQL wildcard characters; change the logic in the
oxfordView.findMany where clause to use the original pathPrefix (not
escapeSqlLikePattern) for the startsWith comparison (i.e. use pathPrefix ?
`${pathPrefix}/` : undefined or similar) and only apply escapeSqlLikePattern
where a SQL LIKE pattern is actually required elsewhere; update references to
escapedPathPrefix accordingly and keep promptName handling the same.
| persist-credentials: false | ||
|
|
||
| - name: Set up QEMU | ||
| uses: docker/setup-qemu-action@v3 |
| uses: docker/setup-qemu-action@v3 | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 |
| type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} | ||
|
|
||
| - name: Build and push ${{ matrix.component }} | ||
| uses: docker/build-push-action@v6 |
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 |
| uses: docker/setup-buildx-action@v3 | ||
|
|
||
| - name: Log in to GitHub Container Registry | ||
| uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v3 |
|
|
||
| - name: Extract metadata for Docker | ||
| id: meta | ||
| uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v5 |
OxfordView Prisma model lacks isActive, createdAt, updatedAt, projectId, tags, and other fields that the Prompt domain type requires. Use `as any` casts at call sites (PromptVersionDiffDialog, ReviewPromptDialog) to unblock the Docker build without widening the shared Prompt type. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (2)
web/src/features/oxford-views/components/oxford-view-history.tsx (1)
126-127: ⚡ Quick winUnsafe
as anycasts may cause runtime errors if the dialog accesses missing Prompt fields.
PromptVersionDiffDialogexpectsPromptobjects with fields likeisActive,createdAt,updatedAt,projectId, andtagsthatOxfordViewVersionlacks. If the dialog accesses these properties, it will receiveundefinedvalues, potentially causing crashes or incorrect rendering.Consider creating a shared interface or an adapter function that maps
OxfordViewVersionto the expected shape with sensible defaults for missing fields:Example adapter approach
// In a shared location function toPromptLike(view: OxfordViewVersion): PromptLike { return { ...view, isActive: false, projectId: "", // or pass from props tags: [], // other required fields with defaults }; }<PromptVersionDiffDialog isOpen={isDiffOpen} setIsOpen={(open) => setIsDiffOpen(open)} - leftPrompt={view as any} - rightPrompt={props.currentView as any} + leftPrompt={toPromptLike(view)} + rightPrompt={toPromptLike(props.currentView)} />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/features/oxford-views/components/oxford-view-history.tsx` around lines 126 - 127, The leftPrompt/rightPrompt props currently cast OxfordViewVersion to any when rendering PromptVersionDiffDialog, which can supply missing fields and cause runtime errors; create an adapter function (e.g., toPromptLike) that maps OxfordViewVersion to the dialog's expected Prompt shape (or a PromptLike interface) and supply sensible defaults for fields like isActive, createdAt/updatedAt, projectId, and tags, then pass the adapted objects to PromptVersionDiffDialog instead of using as any.web/src/features/oxford-views/components/NewOxfordViewForm/index.tsx (1)
420-435: ⚡ Quick winSame
as anycast issue—ReviewPromptDialogmay access fields missing fromOxfordViewLike.The
OxfordViewLiketype (lines 34-42) explicitly omits fields thatReviewPromptDialoglikely expects (e.g.,isActive,createdAt,projectId). Casting toanyhides these mismatches at compile time but may cause runtime issues.Use the same adapter approach recommended for
PromptVersionDiffDialogto provide defaults for missing fields rather than bypassing type safety.Suggested fix
<ReviewPromptDialog - initialPrompt={initialPrompt as any} + initialPrompt={toPromptLike(initialPrompt)} getNewPromptValues={form.getValues} isLoading={createMutation.isPending} onConfirm={form.handleSubmit(onSubmit)} >🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/features/oxford-views/components/NewOxfordViewForm/index.tsx` around lines 420 - 435, initialPrompt is being cast to any when passed to ReviewPromptDialog which hides missing fields from OxfordViewLike; create an adapter function (similar to the one used for PromptVersionDiffDialog) that maps an OxfordViewLike into the full shape ReviewPromptDialog expects by supplying default values for omitted fields (e.g., isActive, createdAt, projectId, etc.), then pass the adapted value (not as any) into ReviewPromptDialog and keep getNewPromptValues={form.getValues} and onConfirm={form.handleSubmit(onSubmit)} unchanged so type-safety is preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@web/src/features/oxford-views/components/NewOxfordViewForm/index.tsx`:
- Around line 420-435: initialPrompt is being cast to any when passed to
ReviewPromptDialog which hides missing fields from OxfordViewLike; create an
adapter function (similar to the one used for PromptVersionDiffDialog) that maps
an OxfordViewLike into the full shape ReviewPromptDialog expects by supplying
default values for omitted fields (e.g., isActive, createdAt, projectId, etc.),
then pass the adapted value (not as any) into ReviewPromptDialog and keep
getNewPromptValues={form.getValues} and onConfirm={form.handleSubmit(onSubmit)}
unchanged so type-safety is preserved.
In `@web/src/features/oxford-views/components/oxford-view-history.tsx`:
- Around line 126-127: The leftPrompt/rightPrompt props currently cast
OxfordViewVersion to any when rendering PromptVersionDiffDialog, which can
supply missing fields and cause runtime errors; create an adapter function
(e.g., toPromptLike) that maps OxfordViewVersion to the dialog's expected Prompt
shape (or a PromptLike interface) and supply sensible defaults for fields like
isActive, createdAt/updatedAt, projectId, and tags, then pass the adapted
objects to PromptVersionDiffDialog instead of using as any.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0e923690-88e6-416c-9d85-8d806d3f81b7
📒 Files selected for processing (2)
web/src/features/oxford-views/components/NewOxfordViewForm/index.tsxweb/src/features/oxford-views/components/oxford-view-history.tsx
jsonSchema is exported from @langfuse/shared, not @langfuse/shared/src/server. Fixes Docker build Type error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
oxford_views is not in the allowed table name union type; the oxford views router reuses the same prompts table column definitions so 'prompts' is the correct alias. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d type tabs
- Remove Text/Chat type selector — Oxford Views only accept string arrays
- Remove the Config JSON field entirely
- Replace content editor with a dynamic add/remove string entry list
- Update validation schema to z.array(z.string())
- Update tRPC create procedure to use dedicated CreateOxfordViewSchema
- Hardcode type='text' and config={} on write; remove PromptContentSchema
and jsonSchema parse calls from read/copy paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
web/src/features/oxford-views/server/routers/oxfordViewRouter.ts (3)
729-739:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDon’t escape
pathPrefixfor PrismastartsWithin delete flow.Using escaped values in Prisma prefix matching can miss valid folder names containing
%or_.Suggested fix
- const escapedPathPrefix = pathPrefix - ? escapeSqlLikePattern(pathPrefix) - : undefined; - const views = await ctx.prisma.oxfordView.findMany({ where: { projectId, name: promptName ? promptName - : { startsWith: `${escapedPathPrefix}/` }, + : { startsWith: `${pathPrefix}/` }, }, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/features/oxford-views/server/routers/oxfordViewRouter.ts` around lines 729 - 739, The delete flow currently uses escapedPathPrefix (produced by escapeSqlLikePattern) for Prisma startsWith, which causes matches to miss folder names containing '%' or '_'; change the logic in the ctx.prisma.oxfordView.findMany call so that when promptName is falsy you pass the raw pathPrefix (not escapedPathPrefix) into the startsWith clause (or undefined if pathPrefix is falsy), remove reliance on escapeSqlLikePattern for this Prisma query, and keep promptName handling unchanged; locate the code around escapedPathPrefix, escapeSqlLikePattern, pathPrefix, promptName and ctx.prisma.oxfordView.findMany to make this change.
671-675:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winEnforce RBAC before returning filter options.
filterOptionsreads project-scoped names/tags/labels withoutthrowIfNoProjectAccess, allowing metadata enumeration by unauthorized users.Suggested fix
filterOptions: protectedProjectProcedure .input(z.object({ projectId: z.string() })) .query(async ({ input, ctx }) => { + throwIfNoProjectAccess({ + session: ctx.session, + projectId: input.projectId, + scope: "prompts:read", + }); + const [names, tags, labels] = await Promise.all([🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/features/oxford-views/server/routers/oxfordViewRouter.ts` around lines 671 - 675, The filterOptions query (defined on protectedProjectProcedure as filterOptions) fetches project-scoped names/tags/labels before enforcing RBAC; call the existing RBAC guard throwIfNoProjectAccess for the incoming input.projectId at the start of the .query handler (before any ctx.prisma calls) so unauthorized users cannot enumerate metadata — i.e., invoke throwIfNoProjectAccess(input.projectId, ctx) or the project access helper available on ctx, await it, then proceed with the ctx.prisma.groupBy calls to return names/tags/labels.
573-588:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUse raw path values for Prisma
startsWithin folder duplication.Escaping
sourcePath/targetPathbefore PrismastartsWithcauses false negatives for names containing%or_.Suggested fix
- const escapedTarget = escapeSqlLikePattern(targetPath); - const escapedSource = escapeSqlLikePattern(sourcePath); - const existingTarget = await ctx.prisma.oxfordView.findFirst({ - where: { projectId, name: { startsWith: `${escapedTarget}/` } }, + where: { projectId, name: { startsWith: `${targetPath}/` } }, }); @@ const sourceViews = await ctx.prisma.oxfordView.findMany({ - where: { projectId, name: { startsWith: `${escapedSource}/` } }, + where: { projectId, name: { startsWith: `${sourcePath}/` } }, orderBy: [{ name: "asc" }, { version: "asc" }], });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/features/oxford-views/server/routers/oxfordViewRouter.ts` around lines 573 - 588, The code is escaping sourcePath/targetPath via escapeSqlLikePattern before passing to Prisma's startsWith, which breaks matches containing '%' or '_'; update the queries that use ctx.prisma.oxfordView.findFirst (existingTarget) and findMany (sourceViews) to use the raw targetPath and sourcePath (e.g., `${targetPath}/` and `${sourcePath}/`) for the startsWith conditions instead of escapedTarget/escapedSource, and keep escapeSqlLikePattern only for raw SQL LIKE usages if any.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@web/src/features/oxford-views/server/routers/oxfordViewRouter.ts`:
- Line 34: CreateOxfordViewSchema currently allows an empty prompt array; update
the prompt schema (where prompt: z.array(z.string().min(1))) to require at least
one entry by adding an array length check (e.g., prompt:
z.array(z.string().min(1)).nonempty() or prompt:
z.array(z.string().min(1)).min(1)) so API callers cannot create views with an
empty prompt.
---
Duplicate comments:
In `@web/src/features/oxford-views/server/routers/oxfordViewRouter.ts`:
- Around line 729-739: The delete flow currently uses escapedPathPrefix
(produced by escapeSqlLikePattern) for Prisma startsWith, which causes matches
to miss folder names containing '%' or '_'; change the logic in the
ctx.prisma.oxfordView.findMany call so that when promptName is falsy you pass
the raw pathPrefix (not escapedPathPrefix) into the startsWith clause (or
undefined if pathPrefix is falsy), remove reliance on escapeSqlLikePattern for
this Prisma query, and keep promptName handling unchanged; locate the code
around escapedPathPrefix, escapeSqlLikePattern, pathPrefix, promptName and
ctx.prisma.oxfordView.findMany to make this change.
- Around line 671-675: The filterOptions query (defined on
protectedProjectProcedure as filterOptions) fetches project-scoped
names/tags/labels before enforcing RBAC; call the existing RBAC guard
throwIfNoProjectAccess for the incoming input.projectId at the start of the
.query handler (before any ctx.prisma calls) so unauthorized users cannot
enumerate metadata — i.e., invoke throwIfNoProjectAccess(input.projectId, ctx)
or the project access helper available on ctx, await it, then proceed with the
ctx.prisma.groupBy calls to return names/tags/labels.
- Around line 573-588: The code is escaping sourcePath/targetPath via
escapeSqlLikePattern before passing to Prisma's startsWith, which breaks matches
containing '%' or '_'; update the queries that use
ctx.prisma.oxfordView.findFirst (existingTarget) and findMany (sourceViews) to
use the raw targetPath and sourcePath (e.g., `${targetPath}/` and
`${sourcePath}/`) for the startsWith conditions instead of
escapedTarget/escapedSource, and keep escapeSqlLikePattern only for raw SQL LIKE
usages if any.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5245d9c3-f3c4-4378-ac9c-fefb3483d9b2
📒 Files selected for processing (3)
web/src/features/oxford-views/components/NewOxfordViewForm/index.tsxweb/src/features/oxford-views/components/NewOxfordViewForm/validation.tsweb/src/features/oxford-views/server/routers/oxfordViewRouter.ts
| const CreateOxfordViewSchema = z.object({ | ||
| projectId: z.string(), | ||
| name: PromptNameSchema, | ||
| prompt: z.array(z.string().min(1)), |
There was a problem hiding this comment.
Require at least one prompt entry in the server schema.
CreateOxfordViewSchema currently accepts an empty prompt array, so direct API callers can create empty views even though the UI blocks it.
Suggested fix
- prompt: z.array(z.string().min(1)),
+ prompt: z.array(z.string().min(1)).min(1, "Add at least one entry"),📝 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.
| prompt: z.array(z.string().min(1)), | |
| prompt: z.array(z.string().min(1)).min(1, "Add at least one entry"), |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/features/oxford-views/server/routers/oxfordViewRouter.ts` at line 34,
CreateOxfordViewSchema currently allows an empty prompt array; update the prompt
schema (where prompt: z.array(z.string().min(1))) to require at least one entry
by adding an array length check (e.g., prompt:
z.array(z.string().min(1)).nonempty() or prompt:
z.array(z.string().min(1)).min(1)) so API callers cannot create views with an
empty prompt.
OxfordView.config is JsonValue (includes null) but createMany input requires InputJsonValue. Fallback to empty object fixes the type error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JsonValue includes null which is not assignable to InputJsonValue.
Cast (v.config ?? {}) as Prisma.InputJsonValue in both copy/duplicate
procedures and fix the toCreate type annotation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GET /api/public/oxford-views and GET /api/public/oxford-views/:viewName fetch a view by name/label/version; POST creates a new version. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
web/src/pages/api/public/oxford-views/[viewName]/index.ts (1)
32-125: 🏗️ Heavy liftConsider using
withMiddlewaresfor public API routes.Per coding guidelines, public API routes in
src/pages/api/public/**should usesrc/features/public-api/server/withMiddlewares.tsrather than implementing the middleware chain manually. This ensures consistent auth, CORS, rate limiting, and error handling patterns across all public endpoints.Additionally, strict request and response types should be defined in
src/features/public-api/types/*for API contract consistency.As per coding guidelines: "Public API routes should use
src/features/public-api/server/withMiddlewares.ts, define strict request and response types insrc/features/public-api/types/*, add server tests, and update Fern sources when the contract changes"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/pages/api/public/oxford-views/`[viewName]/index.ts around lines 32 - 125, The handler in this public API route manually runs cors, auth, rate limiting, telemetry and error handling; replace this pattern by wrapping the exported handler with the centralized withMiddlewares so the route uses src/features/public-api/server/withMiddlewares.ts for CORS/auth/rate limits and unified error handling. Refactor the current exported async function handler to a lean business-logic function (preserving logic that looks up the oxfordView, e.g., QuerySchema.parse, prisma.oxfordView queries, PRODUCTION_LABEL usage and the JSON response shape) and export it via withMiddlewares(...) so ApiAuthService/RateLimitService specifics are handled by the middleware; also add/consume the strict request/response types from src/features/public-api/types to define the route contract and update tests/Fern sources as needed.web/src/pages/api/public/oxford-views/index.ts (1)
46-224: 🏗️ Heavy liftConsider using
withMiddlewaresfor public API routes.Same as the
[viewName]endpoint—this route should usesrc/features/public-api/server/withMiddlewares.tsand define strict request/response types insrc/features/public-api/types/*per the coding guidelines for public API routes.As per coding guidelines: "Public API routes should use
src/features/public-api/server/withMiddlewares.ts, define strict request and response types insrc/features/public-api/types/*, add server tests, and update Fern sources when the contract changes"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/pages/api/public/oxford-views/index.ts` around lines 46 - 224, The handler function currently implements middleware, auth, validation and response logic inline; refactor it to use the public API helper by wrapping the exported handler with withMiddlewares (from src/features/public-api/server/withMiddlewares.ts) and move request/response shapes into strict types/schemas under src/features/public-api/types (create GetOxfordViewRequest/Response and CreateOxfordViewRequest/Response types or Zod schemas), update handler signature to accept typed req/res or a typed context as required by withMiddlewares, replace inline runMiddleware/cors usage with the withMiddlewares pipeline, and ensure the route still calls ApiAuthService, RateLimitService, prisma lookups (oxfordView queries), and error handling unchanged; also add/adjust server tests and Fern contract updates to reflect the new typed request/response contracts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@web/src/pages/api/public/oxford-views/index.ts`:
- Around line 151-158: The current Promise.all over previousWithProduction
calling prisma.oxfordView.update can leave partial updates if one update fails;
replace this with a transactional single-operation approach—either run the
per-record updates inside a prisma.$transaction (use
prisma.$transaction(previousWithProduction.map(v =>
prisma.oxfordView.update(...))) so all updates commit or roll back together) or
perform a single update via a raw query within a transaction (e.g. use
tx.$executeRaw to array_remove PRODUCTION_LABEL from labels for matching
projectId/name/ANY(labels)). Ensure you reference previousWithProduction,
PRODUCTION_LABEL, and prisma.oxfordView.update (or tx.$executeRaw) when making
the change.
- Around line 134-185: The current flow that computes newVersion by calling
prisma.oxfordView.findFirst and then updates/removes labels and creates a new
row (prisma.oxfordView.findMany, prisma.oxfordView.update,
prisma.oxfordView.create) is racy and not transactional; wrap the multi-step
logic in a single Prisma transaction to obtain atomicity and use row-level
locking or a DB-unique constraint to prevent duplicate versions: start a
transaction, re-read the latest row for (projectId, name) inside the transaction
(or rely on a unique constraint on (projectId, name, version) so conflicting
creates fail), perform the label updates and create the new row within that
transaction, and handle transaction errors by retrying/create-failure detection
so concurrent requests either serialize or one fails cleanly. Ensure the
functions referenced (the blocks using prisma.oxfordView.findFirst, findMany,
update, create) are moved inside the transaction scope and add/ensure a database
unique constraint on (projectId, name, version) to provide a final safeguard.
---
Nitpick comments:
In `@web/src/pages/api/public/oxford-views/`[viewName]/index.ts:
- Around line 32-125: The handler in this public API route manually runs cors,
auth, rate limiting, telemetry and error handling; replace this pattern by
wrapping the exported handler with the centralized withMiddlewares so the route
uses src/features/public-api/server/withMiddlewares.ts for CORS/auth/rate limits
and unified error handling. Refactor the current exported async function handler
to a lean business-logic function (preserving logic that looks up the
oxfordView, e.g., QuerySchema.parse, prisma.oxfordView queries, PRODUCTION_LABEL
usage and the JSON response shape) and export it via withMiddlewares(...) so
ApiAuthService/RateLimitService specifics are handled by the middleware; also
add/consume the strict request/response types from src/features/public-api/types
to define the route contract and update tests/Fern sources as needed.
In `@web/src/pages/api/public/oxford-views/index.ts`:
- Around line 46-224: The handler function currently implements middleware,
auth, validation and response logic inline; refactor it to use the public API
helper by wrapping the exported handler with withMiddlewares (from
src/features/public-api/server/withMiddlewares.ts) and move request/response
shapes into strict types/schemas under src/features/public-api/types (create
GetOxfordViewRequest/Response and CreateOxfordViewRequest/Response types or Zod
schemas), update handler signature to accept typed req/res or a typed context as
required by withMiddlewares, replace inline runMiddleware/cors usage with the
withMiddlewares pipeline, and ensure the route still calls ApiAuthService,
RateLimitService, prisma lookups (oxfordView queries), and error handling
unchanged; also add/adjust server tests and Fern contract updates to reflect the
new typed request/response contracts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 27e6ec43-f589-4d3b-a4d0-d0c80bee4f62
📒 Files selected for processing (3)
web/src/features/oxford-views/server/routers/oxfordViewRouter.tsweb/src/pages/api/public/oxford-views/[viewName]/index.tsweb/src/pages/api/public/oxford-views/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- web/src/features/oxford-views/server/routers/oxfordViewRouter.ts
| const latest = await prisma.oxfordView.findFirst({ | ||
| where: { projectId, name: input.name }, | ||
| orderBy: { version: "desc" }, | ||
| }); | ||
|
|
||
| const newVersion = (latest?.version ?? 0) + 1; | ||
| const finalLabels = [...new Set([...input.labels, LATEST_PROMPT_LABEL])]; | ||
|
|
||
| // Promote production label: strip it from previous versions if present | ||
| if (finalLabels.includes(PRODUCTION_LABEL)) { | ||
| const previousWithProduction = await prisma.oxfordView.findMany({ | ||
| where: { | ||
| projectId, | ||
| name: input.name, | ||
| labels: { has: PRODUCTION_LABEL }, | ||
| }, | ||
| }); | ||
| await Promise.all( | ||
| previousWithProduction.map((v) => | ||
| prisma.oxfordView.update({ | ||
| where: { id: v.id }, | ||
| data: { labels: v.labels.filter((l) => l !== PRODUCTION_LABEL) }, | ||
| }), | ||
| ), | ||
| ); | ||
| } | ||
|
|
||
| // Strip LATEST_PROMPT_LABEL from the previous latest version | ||
| if (latest) { | ||
| await prisma.oxfordView.update({ | ||
| where: { id: latest.id }, | ||
| data: { | ||
| labels: latest.labels.filter((l) => l !== LATEST_PROMPT_LABEL), | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| const created = await prisma.oxfordView.create({ | ||
| data: { | ||
| id: uuidv4(), | ||
| name: input.name, | ||
| prompt: input.prompt, | ||
| version: newVersion, | ||
| labels: finalLabels, | ||
| type: "text", | ||
| config: {}, | ||
| tags: [], | ||
| commitMessage: input.commitMessage ?? null, | ||
| createdBy: "API", | ||
| project: { connect: { id: projectId } }, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Race condition: concurrent POST requests can create duplicate version numbers.
The version calculation reads the latest version and increments it without any locking or atomic operation. Two concurrent requests for the same view name will both read the same latest.version, compute the same newVersion, and create records with duplicate version numbers.
Additionally, the multi-step operation (find latest → update previous labels → create new) is not wrapped in a transaction. If any intermediate step fails, the database state can become inconsistent (e.g., LATEST_PROMPT_LABEL stripped from old version but new version not created).
🔒 Recommended fix: wrap in a transaction with row-level locking
- const latest = await prisma.oxfordView.findFirst({
- where: { projectId, name: input.name },
- orderBy: { version: "desc" },
- });
-
- const newVersion = (latest?.version ?? 0) + 1;
- const finalLabels = [...new Set([...input.labels, LATEST_PROMPT_LABEL])];
-
- // Promote production label: strip it from previous versions if present
- if (finalLabels.includes(PRODUCTION_LABEL)) {
- const previousWithProduction = await prisma.oxfordView.findMany({
+ const created = await prisma.$transaction(async (tx) => {
+ // Lock rows for this project+name to prevent concurrent version conflicts
+ const latest = await tx.oxfordView.findFirst({
+ where: { projectId, name: input.name },
+ orderBy: { version: "desc" },
+ });
+
+ const newVersion = (latest?.version ?? 0) + 1;
+ const finalLabels = [...new Set([...input.labels, LATEST_PROMPT_LABEL])];
+
+ // Promote production label: strip it from previous versions if present
+ if (finalLabels.includes(PRODUCTION_LABEL)) {
+ await tx.oxfordView.updateMany({
+ where: {
+ projectId,
+ name: input.name,
+ labels: { has: PRODUCTION_LABEL },
+ },
+ data: {
+ // Note: updateMany doesn't support array filtering directly;
+ // consider using raw SQL or iterating within the transaction
+ },
+ });
+ }
+
+ // Strip LATEST_PROMPT_LABEL from the previous latest version
+ if (latest) {
+ await tx.oxfordView.update({
+ where: { id: latest.id },
+ data: {
+ labels: latest.labels.filter((l) => l !== LATEST_PROMPT_LABEL),
+ },
+ });
+ }
+
+ return tx.oxfordView.create({
+ data: {
+ id: uuidv4(),
+ name: input.name,
+ prompt: input.prompt,
+ version: newVersion,
+ labels: finalLabels,
+ type: "text",
+ config: {},
+ tags: [],
+ commitMessage: input.commitMessage ?? null,
+ createdBy: "API",
+ project: { connect: { id: projectId } },
+ },
+ });
+ });For stronger guarantees against version conflicts, consider adding a unique constraint on (projectId, name, version) in the database schema, which would cause concurrent duplicates to fail at the DB level.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/pages/api/public/oxford-views/index.ts` around lines 134 - 185, The
current flow that computes newVersion by calling prisma.oxfordView.findFirst and
then updates/removes labels and creates a new row (prisma.oxfordView.findMany,
prisma.oxfordView.update, prisma.oxfordView.create) is racy and not
transactional; wrap the multi-step logic in a single Prisma transaction to
obtain atomicity and use row-level locking or a DB-unique constraint to prevent
duplicate versions: start a transaction, re-read the latest row for (projectId,
name) inside the transaction (or rely on a unique constraint on (projectId,
name, version) so conflicting creates fail), perform the label updates and
create the new row within that transaction, and handle transaction errors by
retrying/create-failure detection so concurrent requests either serialize or one
fails cleanly. Ensure the functions referenced (the blocks using
prisma.oxfordView.findFirst, findMany, update, create) are moved inside the
transaction scope and add/ensure a database unique constraint on (projectId,
name, version) to provide a final safeguard.
| await Promise.all( | ||
| previousWithProduction.map((v) => | ||
| prisma.oxfordView.update({ | ||
| where: { id: v.id }, | ||
| data: { labels: v.labels.filter((l) => l !== PRODUCTION_LABEL) }, | ||
| }), | ||
| ), | ||
| ); |
There was a problem hiding this comment.
Promise.all can leave partial updates on failure.
If one of the update calls in Promise.all fails while others succeed, some records will have PRODUCTION_LABEL stripped while others retain it. Consider using updateMany with a single query or ensuring all updates are within the same transaction.
♻️ Suggested improvement using updateMany pattern
Note: Prisma's updateMany doesn't directly support array element removal. Within a transaction, you could use raw SQL or keep the iteration but ensure it's all within a single transaction scope (as suggested above).
Alternatively, if the label array manipulation is complex, consider a raw query:
await tx.$executeRaw`
UPDATE "OxfordView"
SET labels = array_remove(labels, ${PRODUCTION_LABEL})
WHERE "projectId" = ${projectId}
AND name = ${input.name}
AND ${PRODUCTION_LABEL} = ANY(labels)
`;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/pages/api/public/oxford-views/index.ts` around lines 151 - 158, The
current Promise.all over previousWithProduction calling prisma.oxfordView.update
can leave partial updates if one update fails; replace this with a transactional
single-operation approach—either run the per-record updates inside a
prisma.$transaction (use prisma.$transaction(previousWithProduction.map(v =>
prisma.oxfordView.update(...))) so all updates commit or roll back together) or
perform a single update via a raw query within a transaction (e.g. use
tx.$executeRaw to array_remove PRODUCTION_LABEL from labels for matching
projectId/name/ANY(labels)). Ensure you reference previousWithProduction,
PRODUCTION_LABEL, and prisma.oxfordView.update (or tx.$executeRaw) when making
the change.
What does this PR do?
Fixes # (issue)
Type of change
Mandatory Tasks
Checklist
pnpm run format)npm run lint)Summary by CodeRabbit
New Features
Backend