Skip to content

Add lexicon ZIP upload to Lexicons page#35

Merged
Kzoeps merged 13 commits intomainfrom
upload-lexicons-json
Apr 29, 2026
Merged

Add lexicon ZIP upload to Lexicons page#35
Kzoeps merged 13 commits intomainfrom
upload-lexicons-json

Conversation

@Kzoeps
Copy link
Copy Markdown
Collaborator

@Kzoeps Kzoeps commented Apr 29, 2026

Summary

  • Adds an admin-only ZIP upload section to the Lexicons page for unpublished lexicon JSON archives.
  • Reuses the existing uploadLexicons(zipBase64:) admin GraphQL mutation and refreshes the lexicon list after upload.
  • Fixes pre-existing frontend lint failures and clears the ZIP picker after successful upload.

Validation

  • npm --prefix client run lint
  • npm --prefix client run test
  • npm --prefix client run build
  • node client/scripts/generate-jwk.js

Notes

  • Uploaded lexicons may still require a backend restart before appearing in the public typed GraphQL schema.

Summary by CodeRabbit

  • New Features

    • Admin-only ZIP-based lexicon upload flow with validation, upload state, and success/error feedback; lexicons UI grouped for register and upload.
  • Refactor

    • Simplified theme toggle and JavaScript highlighting logic to improve client rendering and runtime behavior.
  • Chores

    • Removed unused imports, updated key-generation script internals, added a release changeset, and small config updates.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 29, 2026

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

Project Deployment Actions Updated (UTC)
hyperindex-atproto-client Ready Ready Preview, Comment Apr 29, 2026 11:47am
hyperindex-client Ready Ready Preview, Comment Apr 29, 2026 11:47am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

Warning

Rate limit exceeded

@Kzoeps has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 17 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4b6bfe40-d9c9-4c2a-b753-b50682a9a813

📥 Commits

Reviewing files that changed from the base of the PR and between 1dcd19f and 7ea2334.

📒 Files selected for processing (5)
  • .changes/unreleased/route-port-redirects-through-proxy.yaml
  • AGENTS.md
  • README.md
  • client/README.md
  • client/src/proxy.ts
📝 Walkthrough

Walkthrough

Switches Node crypto usage to node:crypto, simplifies JS highlighting and a few UI types/imports, refactors theme mount detection to useSyncExternalStore, adds an admin ZIP lexicon upload flow and GraphQL mutation, updates Next config/env example, and adds a localhost->127.0.0.1 proxy route.

Changes

Cohort / File(s) Summary
Crypto script
client/scripts/generate-jwk.js
Move crypto import to a dynamic node:crypto import inside the function; use webcrypto.subtle for key generation/export.
Docs & small UI edits
client/src/app/docs/page.tsx, client/src/components/ui/Button.tsx, client/src/components/ui/Card.tsx
Remove unused vars/regex in JS highlighter; drop unused React import; change CardProps from interface to type alias.
Theme toggle refactor
client/src/components/ThemeToggle.tsx
Replace mount gating (useState+useEffect) with useSyncExternalStore; use resolvedTheme and preserve placeholder render.
Lexicons ZIP upload + UI
client/src/app/lexicons/page.tsx, .changes/unreleased/add-lexicon-zip-upload-ui.yaml
Add admin-only ZIP upload UI, UPLOAD_LEXICONS mutation, FileReader base64 read, upload state/validation, reset file input, and invalidate ["lexicons"] query; add changeset.
Next config & env
client/next.config.ts, client/.env.example
Add skipProxyUrlNormalize: true to Next config; update example env URLs to https://dev.api.hi.gainforest.app.
Proxy route
client/src/proxy.ts
Add route handler proxy(request: NextRequest) and config = { matcher: "/:path*" } to rewrite localhost: host redirects to 127.0.0.1 and return a 307 redirect.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Browser
  participant FileReader
  participant GraphQL_API
  participant QueryCache

  User->>Browser: choose .zip file in admin upload form
  Browser->>FileReader: readAsDataURL(file)
  FileReader-->>Browser: base64-encoded ZIP
  Browser->>GraphQL_API: UPLOAD_LEXICONS(base64Zip)
  GraphQL_API-->>Browser: upload response (count / error)
  Browser->>QueryCache: invalidate ["lexicons"]
  Browser-->>User: show success/error and reset file input
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I found a ZIP beneath soft loam,
I nudged the theme until it shone,
Node whispered keys in quiet code,
Lexicons hopped down the upload road,
Tiny paws, a tidy home. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add lexicon ZIP upload to Lexicons page' is concise, specific, and directly describes the main change—adding a ZIP upload feature to the Lexicons page, which is the primary focus of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch upload-lexicons-json

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

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 51 minutes and 17 seconds.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
client/src/app/docs/page.tsx (1)

71-81: ⚠️ Potential issue | 🟡 Minor

Guard overlapping tokens to avoid malformed highlight output.

highlightJS can process overlapping tokens (e.g., keyword inside string), which may render duplicate fragments and move lastEnd backward.

💡 Suggested fix
-  tokens.forEach((token) => {
+  tokens.forEach((token) => {
+    if (token.start < lastEnd) {
+      return;
+    }
     if (token.start > lastEnd) {
       parts.push(<span key={key++}>{line.slice(lastEnd, token.start)}</span>);
     }
     if (token.type === "keyword") {
       parts.push(<span key={key++} className="text-purple-400 font-medium">{token.text}</span>);
     } else if (token.type === "string") {
       parts.push(<span key={key++} className="text-amber-300">{token.text}</span>);
     }
-    lastEnd = token.end;
+    lastEnd = Math.max(lastEnd, token.end);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/app/docs/page.tsx` around lines 71 - 81, Tokens may overlap
causing lastEnd to move backward and produce duplicate/malformed fragments; in
the tokens.forEach loop (tokens.forEach) guard against overlaps by skipping any
token where token.end <= lastEnd, compute startPos = Math.max(lastEnd,
token.start) before pushing fragments, use the token's substring corresponding
to [startPos, token.end] (i.e., token.text.slice(startPos - token.start,
token.end - token.start)) when pushing highlighted spans into parts, and finally
set lastEnd = Math.max(lastEnd, token.end) so lastEnd never moves backward.
client/scripts/generate-jwk.js (1)

11-26: ⚠️ Potential issue | 🟡 Minor

Set non-zero exit code on error in catch handler

When generateJWK() rejects, the catch block logs the error but the process still exits with code 0. This can cause CI/build checks to incorrectly pass. Update line 38 to:

generateJWK().catch((err) => {
  console.error(err)
  process.exitCode = 1
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/scripts/generate-jwk.js` around lines 11 - 26, The current top-level
caller of generateJWK() logs errors but does not set a non-zero exit code on
failure; update the promise rejection handler for generateJWK() to set
process.exitCode = 1 after logging the error (e.g., in the catch for
generateJWK()) so CI sees failures; locate the invocation of generateJWK() and
its .catch(...) and change it to log the error then set process.exitCode = 1.
🧹 Nitpick comments (1)
client/scripts/generate-jwk.js (1)

38-38: Script should exit non-zero on failure

generateJWK().catch(console.error) logs the error but doesn’t guarantee a failing exit code. For CI, better to set process.exitCode = 1 (or process.exit(1)).

🔧 Proposed fix
-generateJWK().catch(console.error)
+generateJWK().catch((err) => {
+  console.error(err)
+  process.exitCode = 1
+})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/scripts/generate-jwk.js` at line 38, The current top-level call
generateJWK().catch(console.error) only logs failures but leaves process exit
code as zero; update the catch handler for generateJWK to both log the error and
set a non-zero exit (e.g., set process.exitCode = 1 or call process.exit(1)) so
CI sees failures—replace the current trailing call with a catch that logs the
error and sets the exit code accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/src/app/lexicons/page.tsx`:
- Around line 374-388: Prevent duplicate submits by guarding handleUpload with
an "in-progress" check and setting/resetting that flag before the FileReader
starts and after it completes/error. Specifically, add a local state like
isUploading (useState) and at the top of handleUpload return early if
uploadMutation.isPending || isUploading; set isUploading = true immediately
before calling reader.readAsDataURL(zipFile), and set isUploading = false in
both reader.onload and reader.onerror (after calling uploadMutation.mutate or
setError/setSuccess). Apply the same pattern to the other upload handler
referenced (the one around the other submit block) and ensure any submit
button/form disabled prop uses uploadMutation.isPending || isUploading.
- Around line 445-453: The textarea using state nsidInput (with onChange calling
setNsidInput) and the other input around lines 483-491 lack programmatic labels;
add either a <label htmlFor="..."> tied to a unique id on each control or add an
aria-label attribute on the textarea and the other input so assistive tech can
identify them (e.g., give the textarea id="nsid-input" and label/aria-label
"NSIDs" and apply a similar id/label/aria-label for the second control
referenced in that block).
- Around line 266-268: Import and use useAdminSession in
client/src/app/lexicons/page.tsx and gate the uploadMutation by the admin flag:
call const { isAdmin } = useAdminSession() and pass enabled: isAdmin to the
useMutation for uploadMutation (or conditionally render/disable the upload UI
when isAdmin is false) so only admins can trigger the UPLOAD_LEXICONS mutation;
ensure references to uploadMutation, UPLOAD_LEXICONS and the upload UI are
updated to respect isAdmin.

---

Outside diff comments:
In `@client/scripts/generate-jwk.js`:
- Around line 11-26: The current top-level caller of generateJWK() logs errors
but does not set a non-zero exit code on failure; update the promise rejection
handler for generateJWK() to set process.exitCode = 1 after logging the error
(e.g., in the catch for generateJWK()) so CI sees failures; locate the
invocation of generateJWK() and its .catch(...) and change it to log the error
then set process.exitCode = 1.

In `@client/src/app/docs/page.tsx`:
- Around line 71-81: Tokens may overlap causing lastEnd to move backward and
produce duplicate/malformed fragments; in the tokens.forEach loop
(tokens.forEach) guard against overlaps by skipping any token where token.end <=
lastEnd, compute startPos = Math.max(lastEnd, token.start) before pushing
fragments, use the token's substring corresponding to [startPos, token.end]
(i.e., token.text.slice(startPos - token.start, token.end - token.start)) when
pushing highlighted spans into parts, and finally set lastEnd =
Math.max(lastEnd, token.end) so lastEnd never moves backward.

---

Nitpick comments:
In `@client/scripts/generate-jwk.js`:
- Line 38: The current top-level call generateJWK().catch(console.error) only
logs failures but leaves process exit code as zero; update the catch handler for
generateJWK to both log the error and set a non-zero exit (e.g., set
process.exitCode = 1 or call process.exit(1)) so CI sees failures—replace the
current trailing call with a catch that logs the error and sets the exit code
accordingly.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1b351beb-8073-47aa-9f23-3e7976fee5d9

📥 Commits

Reviewing files that changed from the base of the PR and between 8ec3475 and 847c26e.

📒 Files selected for processing (6)
  • client/scripts/generate-jwk.js
  • client/src/app/docs/page.tsx
  • client/src/app/lexicons/page.tsx
  • client/src/components/ThemeToggle.tsx
  • client/src/components/ui/Button.tsx
  • client/src/components/ui/Card.tsx

Comment thread client/src/app/lexicons/page.tsx Outdated
Comment thread client/src/app/lexicons/page.tsx
Comment thread client/src/app/lexicons/page.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@client/src/proxy.ts`:
- Around line 7-13: The current redirect only matches "localhost:<port>" and is
case-sensitive, so requests where hostname is "localhost" (no port) or
mixed-case like "LocalHost" are missed; update the check to use
hostname.toLowerCase() and match either exactly "localhost" or starting with
"localhost:"; when building the redirectUrl (used with NextResponse.redirect)
derive the port by checking for a colon in the original host (e.g., const port =
newHost.includes(":") ? newHost.split(":")[1] : "3000") so requests without an
explicit port get a sensible default, and then set redirectUrl.hostname =
"127.0.0.1" and redirectUrl.port = port before returning
NextResponse.redirect(redirectUrl, { status: 307 }).
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 85274036-f2b3-4390-ad89-b2d90d39cdd1

📥 Commits

Reviewing files that changed from the base of the PR and between 545ae7f and 1dcd19f.

📒 Files selected for processing (4)
  • client/.env.example
  • client/next.config.ts
  • client/src/app/lexicons/page.tsx
  • client/src/proxy.ts
✅ Files skipped from review due to trivial changes (1)
  • client/.env.example
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/src/app/lexicons/page.tsx

Comment thread client/src/proxy.ts Outdated
@Kzoeps Kzoeps merged commit b9f581f into main Apr 29, 2026
9 checks passed
@Kzoeps Kzoeps deleted the upload-lexicons-json branch April 29, 2026 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant