docs: fix missing search on mobile, cleanup navbar - #618
Conversation
|
@sanny-io is attempting to deploy a commit to the ZenStack Team on Vercel. A member of the Team first needs to authorize it. |
|
Warning Review limit reached
Next review available in: 16 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
WalkthroughThe site now proxies the Framer landing page at ChangesSite migration and presentation
Documentation refresh
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Browser
participant middleware
participant Framer
Browser->>middleware: Request /
middleware->>Framer: Fetch landing page
Framer-->>middleware: Return HTML
middleware->>middleware: Rewrite Framer URLs
middleware-->>Browser: Return rewritten page
Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
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 |
|
Hi @sanny-io , a friend of mine is actually working on a fully renovated design of the home page 😄, so I'm putting this PR on hold. Hopefully that'll land soon! |
* docs: add PGlite section * add newline
* doc: release 3.8.0 * update
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: new logo * update logo
* chore: update theme to match new branding - Change primary color to #ff3f02 (matching zenstack.dev/framer) - Use IBM Plex Sans as the base font - Style navbar title in orange, all caps, tighter logo gap - Replace logo.png/logo-dark.png with new-logo.png Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: rewrite root to framer landing page Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: move custom home page to /home so root rewrite takes effect Vercel rewrites only apply when no static file matches the path, so the Docusaurus-generated root index.html was shadowing the / -> Framer rewrite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: remove old home page and /framer rewrites The custom home page (moved to /home earlier) is superseded by the Framer-hosted landing page served at the root. Sponsorship/UserLogos components were only used by it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * update framer custom domain --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…enstackhq#629) * fix: rewrite canonical URL of Framer-hosted homepage to zenstack.dev The / route is rewritten to zenstack.framer.website (vercel.json), so the page served at zenstack.dev declared the Framer domain as its canonical and og:url. Add Vercel Edge Middleware that proxies the Framer page and rewrites those URLs to https://zenstack.dev. On any failure it falls through to the existing plain rewrite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: remove log * fix: forward query string and safe request headers to Framer upstream Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: forward referer and x-forwarded-for headers to Framer upstream Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@middleware.ts`:
- Around line 41-46: Update the response-header construction in the
rewritten-response flow to use an explicit allowlist of headers appropriate for
the proxied response, such as content-type and cache-control, instead of copying
upstream.headers and deleting only content-encoding and content-length. Preserve
the existing response status and rewritten body while ensuring Framer-specific
headers are not forwarded.
- Line 31: Update the upstream fetch in the middleware to use a bounded timeout
via an abort signal, and handle its failure so AbortError and other fetch errors
return undefined without blocking homepage requests. Preserve the existing
response handling and fallback behavior for non-HTML responses.
In `@src/css/custom.css`:
- Around line 13-19: Update the `.navbar__title` styling to use a text color
with at least 4.5:1 contrast against its white background. Prefer a dedicated
contrast-safe color token if the existing `--ifm-color-primary` must remain
unchanged; otherwise replace the primary token with a sufficiently darker color
while preserving the related palette.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 9fbcc8c9-5817-49f3-87f3-f0e8a0633f33
⛔ Files ignored due to path filters (3)
static/img/new-logo-full.pngis excluded by!**/*.png,!**/*.pngstatic/img/new-logo.pngis excluded by!**/*.png,!**/*.pngvercel.jsonis excluded by!**/*.json
📒 Files selected for processing (19)
blog/react-table/index.mddocs/community-packages.mddocs/orm/api/omit.mddocs/recipe/databases/postgres.mddocs/reference/plugins/soft-delete.mddocs/reference/zmodel/input-validation.mddocusaurus.config.jsmiddleware.tssrc/components/Sponsorship.tsxsrc/components/UserLogos.tsxsrc/css/custom.csssrc/pages/_components/AICoding.tsxsrc/pages/_components/Notes.tsxsrc/pages/_components/ORM.tsxsrc/pages/_components/Schema.tsxsrc/pages/_components/Service.tsxsrc/pages/_components/ValueProps.tsxsrc/pages/index.module.csssrc/pages/index.tsx
💤 Files with no reviewable changes (10)
- src/pages/_components/Notes.tsx
- src/pages/_components/Schema.tsx
- src/pages/_components/Service.tsx
- src/pages/_components/AICoding.tsx
- src/pages/index.module.css
- src/pages/_components/ORM.tsx
- src/pages/_components/ValueProps.tsx
- src/components/UserLogos.tsx
- src/components/Sponsorship.tsx
- src/pages/index.tsx
| } | ||
| } | ||
|
|
||
| const upstream = await fetch(`${FRAMER_ORIGIN}/${search}`, { headers: requestHeaders }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | sed -n '1,120p'
echo "== middleware locations =="
fd -a 'middleware\.(ts|js)$' . 2>/dev/null || true
echo "== middleware.ts context =="
if [ -f middleware.ts ]; then
wc -l middleware.ts
cat -n middleware.ts | sed -n '1,120p'
fi
echo "== vercel.json relevant =="
if [ -f vercel.json ]; then
cat -n vercel.json
fiRepository: zenstackhq/zenstack-docs
Length of output: 6882
🌐 Web query:
Vercel Edge Middleware Edge fetch AbortController signal timeout documentation
💡 Result:
Vercel's Edge Runtime, which powers Routing Middleware and Edge Functions, natively supports the standard Web fetch API and AbortController / AbortSignal APIs [1][2]. To implement a timeout for a fetch request within Vercel Middleware or Edge Functions, you should use AbortSignal.timeout(ms) [3]. This is the recommended approach for limiting the time an external API call is allowed to take [3]. ### Recommended Pattern You can create a signal that automatically aborts after a specified duration: const controller = new AbortController; const signal = AbortSignal.timeout(5000); // Aborts after 5 seconds try { const response = await fetch('https://api.example.com', { signal }); // Handle response } catch (error) { if (error.name === 'TimeoutError') { // Handle specific timeout } } ### Handling Client Disconnection Vercel automatically provides a request.signal which represents the client's connection state [4][5]. If the user disconnects, this signal will fire, and you can propagate this to your fetch calls to cancel them and save resources [4][6]: export async function GET(request: Request) { const controller = new AbortController; // Abort our fetch if the client disconnects request.signal.addEventListener('abort', => controller.abort); const response = await fetch('https://api.example.com', { signal: controller.signal, }); return response; } ### Key Considerations - Middleware Timeout Limits: Middleware has a strict execution time limit (e.g., 25 seconds) [3]. Setting fetch timeouts lower than this limit helps prevent MIDDLEWARE_INVOCATION_TIMEOUT errors [3][7]. - API Compatibility: The Edge Runtime aims for Web Standard compliance [1]. Ensure you are using modern standards; some older polyfills or manual setTimeout implementations may be unnecessary or lead to I/O errors if not handled within the correct request context [8]. - Troubleshooting: If you encounter issues with signals not triggering as expected, ensure you are testing in a production-like environment, as local development or specific proxy configurations can sometimes impact how abort events are propagated [9].
Citations:
- 1: https://vercel.com/docs/functions/runtimes/edge
- 2: https://nextjs.org/docs/pages/api-reference/edge
- 3: https://vercel.com/docs/errors/middleware_invocation_timeout
- 4: https://vercel.com/docs/functions/functions-api-reference.rsc
- 5: https://vercel.com/docs/functions/functions-api-reference
- 6: Ensure
edgeruntime Pages API/App Route handlersAbortSignalaborts on client disconnect vercel/next.js#51727 - 7: https://vercel.com/docs/errors/edge_function_invocation_timeout
- 8: "Cannot perform I/O on behalf of a different request" error when using signal on a fetch call in middleware vercel/edge-runtime#169
- 9: Abort Signal Not Working with Next.js Edge Functions vercel/next.js#50364
Add a bounded timeout to the upstream fetch.
The middleware matches /, and middleware.ts:31 waits on the Framer fetch without any timeout. Since the middleware falls back to undefined on non-HTML responses, return undefined on signals that are not AbortError; otherwise a slow upstream can block the landing page for all homepage requests.
🤖 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 `@middleware.ts` at line 31, Update the upstream fetch in the middleware to use
a bounded timeout via an abort signal, and handle its failure so AbortError and
other fetch errors return undefined without blocking homepage requests. Preserve
the existing response handling and fallback behavior for non-HTML responses.
| const headers = new Headers(upstream.headers); | ||
| // the body was re-encoded, so the upstream encoding/length no longer apply | ||
| headers.delete('content-encoding'); | ||
| headers.delete('content-length'); | ||
|
|
||
| return new Response(rewritten, { status: upstream.status, headers }); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Allowlist forwarded response headers instead of copying all of them.
Line 41 copies every header from the upstream Framer response, deleting only content-encoding and content-length. Any other header Framer sends (for example set-cookie, a content-security-policy tuned for its own domain, or caching directives scoped to zenstack.framer.website) is forwarded verbatim onto a response served from zenstack.dev. This can leak Framer-scoped cookies onto the docs domain or apply security/caching semantics that do not fit the proxied context.
Build the response headers from an explicit allowlist (for example content-type, cache-control) instead of a denylist.
🔒 Proposed fix to allowlist response headers
- const headers = new Headers(upstream.headers);
- // the body was re-encoded, so the upstream encoding/length no longer apply
- headers.delete('content-encoding');
- headers.delete('content-length');
+ const headers = new Headers();
+ for (const name of ['content-type', 'cache-control']) {
+ const value = upstream.headers.get(name);
+ if (value) {
+ headers.set(name, value);
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const headers = new Headers(upstream.headers); | |
| // the body was re-encoded, so the upstream encoding/length no longer apply | |
| headers.delete('content-encoding'); | |
| headers.delete('content-length'); | |
| return new Response(rewritten, { status: upstream.status, headers }); | |
| const headers = new Headers(); | |
| for (const name of ['content-type', 'cache-control']) { | |
| const value = upstream.headers.get(name); | |
| if (value) { | |
| headers.set(name, value); | |
| } | |
| } | |
| return new Response(rewritten, { status: upstream.status, headers }); |
🤖 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 `@middleware.ts` around lines 41 - 46, Update the response-header construction
in the rewritten-response flow to use an explicit allowlist of headers
appropriate for the proxied response, such as content-type and cache-control,
instead of copying upstream.headers and deleting only content-encoding and
content-length. Preserve the existing response status and rewritten body while
ensuring Framer-specific headers are not forwarded.
| --ifm-color-primary: #ff3f02; | ||
| --ifm-color-primary-dark: #e63902; | ||
| --ifm-color-primary-darker: #cc3202; | ||
| --ifm-color-primary-darkest: #b32c01; | ||
| --ifm-color-primary-light: #ff521b; | ||
| --ifm-color-primary-lighter: #ff6535; | ||
| --ifm-color-primary-lightest: #ff794e; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## locate custom.css"
git ls-files | grep -F 'src/css/custom.css' || true
echo "## relevant custom.css section"
if [ -f src/css/custom.css ]; then
nl -ba src/css/custom.css | sed -n '1,90p'
fi
echo "## search navbar__title and primary usage in css"
rg -n --ignore-case 'navbar__title|var\(--ifm-color-primary|color:|background|text' src/css/custom.css -C 2
echo "## compute relative luminance/contrast for asserted colors vs white"
python3 - <<'PY'
def component(s):
v=int(s,16)/255
return v/12.92 if v<=0.03928 else ((v+0.055)/1.055)**2.4
def contrast(c1,c2):
def lum(hex):
return 1.698*component(hex[1:3])+0.987*component(hex[3:5])+0.317*component(hex[5:])
l1,l2=lum(c1),lum(c2)
return(max(l1,l2)+0.05)/(min(l1,l2)+0.05)
colors = {
"primary": "`#ff3f02`",
"primary-dark": "`#e63902`",
"primary-darker": "`#cc3202`",
"primary-darkest": "`#b32c01`",
"primary-light": "`#ff521b`",
"primary-lighter": "`#ff6535`",
"primary-lightest": "`#ff794e`",
}
for name,hex in colors.items():
c=contrast(hex,"`#ffffff`")
print(f"{name} {hex}: {c:.3f}:1 dark text on white ({c>=4.5='contrast-safe'}")
PYRepository: zenstackhq/zenstack-docs
Length of output: 273
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## changed files/stat"
git diff --stat || true
git diff --name-only || trueRepository: zenstackhq/zenstack-docs
Length of output: 184
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## relevant custom.css section"
awk '{printf "%6d\t%s\n", NR, $0}' src/css/custom.css | sed -n '1,90p'
echo "## search navbar__title and primary usage in css"
grep -InE 'navbar__title|var\(--ifm-color-primary|color:|background|text' src/css/custom.css -C 2 || true
echo "## compute relative luminance/contrast for asserted colors vs white"
python3 - <<'PY'
def component(s):
v=int(s,16)/255
return v/12.92 if v<=0.03928 else ((v+0.055)/1.055)**2.4
def contrast(c1,c2):
l1=1.698*component(c1[1:3])+0.987*component(c1[3:5])+0.317*component(c1[5:])
l2=1.698*component(c2[1:3])+0.987*component(c2[3:5])+0.317*component(c2[5:])
return (max(l1,l2)+0.05)/(min(l1,l2)+0.05)
colors = {
"primary": "`#ff3f02`",
"primary-dark": "`#e63902`",
"primary-darker": "`#cc3202`",
"primary-darkest": "`#b32c01`",
"primary-light": "`#ff521b`",
"primary-lighter�": "`#ff6535`",
"primary-lightest": "`#ff794e`",
}
for name, hex in colors.items():
c=contrast(hex, "`#ffffff`")
print(f"{name} {hex}: {c:.3f}:1 dark text on white ({c >= 4.5=contrast-safe})")
PYRepository: zenstackhq/zenstack-docs
Length of output: 2511
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## compute relative luminance/contrast for exact asserted colors vs white"
python3 - <<'PY'
def component(s):
v = int(s, 16) / 255
return v / 12.92 if v <= 0.03928 else ((v + 0.055) / 1.055) ** 2.4
def contrast(hex1, hex2):
def lum(hex):
return 1.698 * component(hex[1:3]) + 0.987 * component(hex[3:5]) + 0.317 * component(hex[5:])
l1, l2 = lum(hex1), lum(hex2)
return (max(l1, l2) + 0.05) / (min(l1, l2) + 0.05)
colors = {
"root primary": "`#ff3f02`",
"root primary-dark": "`#e63902`",
"root primary-darker": "`#cc3202`",
"root primary-darkest": "`#b32c01`",
"root primary-light": "`#ff521b`",
"root primary-lighter": "`#ff6535`",
"root primary-lightest": "`#ff794e`",
"dark primary": "`#ff521b`",
}
for name, hex in colors.items():
c = contrast(hex, "`#ffffff`")
safe = "contrast-safe" if c >= 4.5 else "below 4.5:1"
print(f"{name} {hex}: {c:.3f}:1 on white ({safe})")
PYRepository: zenstackhq/zenstack-docs
Length of output: 703
Use a higher-contrast color for .navbar__title.
.navbar__title applies --ifm-color-primary on a white background, but #ff3f02 does not pass the 4.5:1 text contrast requirement. Use a different dark text color for this title or add a contrast-safe primary token and reference it instead.
🤖 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 `@src/css/custom.css` around lines 13 - 19, Update the `.navbar__title` styling
to use a text color with at least 4.5:1 contrast against its white background.
Prefer a dedicated contrast-safe color token if the existing
`--ifm-color-primary` must remain unchanged; otherwise replace the primary token
with a sufficiently darker color while preserving the related palette.
|
@ymc9 I've merged the latest changes from the redesign and things still look good. |
The "Star on Github" button was introducing significant complexity to the navbar. This PR simplifies the navbar, which fixes several things.
Before
After
Closes #567
Summary by CodeRabbit
New Features
zenstack-mcpcommunity package.Documentation
Refactor