Skip to content

chore: adopt Oxlint for JS/TS semantics; keep Biome for format/non-JS (#321) - #371

Open
thedavidweng wants to merge 5 commits into
1weiho:mainfrom
thedavidweng:oxlint-biome-split-21be
Open

chore: adopt Oxlint for JS/TS semantics; keep Biome for format/non-JS (#321)#371
thedavidweng wants to merge 5 commits into
1weiho:mainfrom
thedavidweng:oxlint-biome-split-21be

Conversation

@thedavidweng

@thedavidweng thedavidweng commented Jul 26, 2026

Copy link
Copy Markdown

Issue

Closes / implements #321 (parent roadmap #318).

This branch is independent — it branches directly from main and contains only the Oxlint adoption changes. It does not depend on #326, #369, or #370; the lint stack is orthogonal to the runtime/React/Vite migrations.

Exact scope

  • Add Oxlint as the sole JS/TS semantic linter
  • Keep Biome as the sole formatter + CSS/JSON (non-JS) linter
  • Disable Biome JS/TS lint via override (no duplicate diagnostics)
  • Update scripts, contributor docs, and fix findings required for a green tree
  • Do not add ESLint, Prettier, Stylelint, or Oxfmt

Versions before → after

Package Before After
oxlint (none) 1.74.0
oxlint-tsgolint (none) 0.24.0
@biomejs/biome 2.4.12 2.4.12 (unchanged)

Why the old state was a problem

Biome owned JS/TS lint, formatting, and CSS/JSON together. That duplicated parser generations vs the Vite 8 / Oxc / Rolldown stack and limited type-aware / multi-file semantic checks (floating promises, import cycles).

Why these tools/versions were chosen

Breaking changes reviewed

Source changes required

  • Correctness fixes: dead || expression, useless spreads, floating promises (void), hook deps, a11y labels, unbound method on presenter channel, Vitest mock typing / conditional expect
  • Intentional suppressions only where behavior is deliberate (file drop zones; open-transition focused sync; locale-stable toast string)
  • jsx-a11y/prefer-tag-over-role left off (intentional ARIA/role patterns; Biome useSemanticElements was already selectively off for slides/landing)

Tests added or changed

  • print-ready.test.ts: typed vi.fn<() => Promise<void>>()
  • design-plugin.test.ts: replace conditional expect with toEqual
  • No silent snapshot updates

Rule mapping table

Existing Biome JS/TS rule/domain Oxlint equivalent Action Reason
linter.rules.recommended (correctness/suspicious/complexity/…) categories.correctness: error + default plugins enabled Core correctness parity
React domain / hooks (useExhaustiveDependencies) react plugin + react-hooks/exhaustive-deps enabled Stronger than prior Biome effective coverage; intentional deps documented inline
a11y recommended (noStaticElementInteractions, labels, …) jsx-a11y plugin enabled (selected) Match prior a11y intent
a11y useSemanticElements / prefer semantic tags jsx-a11y/prefer-tag-over-role disabled Prior Biome overrides already relaxed this for slides/landing; many intentional role patterns
slides/landing a11y overrides oxlint overrides for **/slides/**, apps/web/components/landing/** mirrored Preserve authoring UX exceptions
Import organization Biome assist.organizeImports retained in Biome Non-conflicting with Oxlint
CSS lint + Tailwind directives Biome CSS linter/parser retained in Biome Oxlint has no CSS scope
JSON/JSONC lint/format Biome retained in Biome Oxlint has no JSON scope
Formatting (quotes, width, semicolons) Biome formatter retained Sole formatter
(new) floating promises typescript/no-floating-promises enabled Reviewed; fire-and-forget marked with void
(new) import cycles import/no-cycle enabled Multi-file graph check
(new) Vitest plugin vitest/* correctness enabled assertFunctionNames includes helpers
Vendored shadcn UI ignore packages/core/src/app/components/ui/** ignored Same as prior Biome exclude
CLI template sources ignore packages/cli/template/** ignored Not a linted workspace package install

No previous JS/TS coverage was dropped silently; gaps above are classified.

Benchmark results

Same machine, Node 24.18.0, pnpm 10.17.0. One warm-up + 10 measured runs (median / p95). Times for wrapped pnpm/pnpm exec include package-manager startup.

Metric Current Biome-only (pre-change) Oxlint JS/TS (no type-aware) Oxlint JS/TS (type-aware) Final combined pnpm lint Change vs current
warm median elapsed 0.605 s 0.598 s 1.168 s 1.918 s combined +1.31 s (adds type-aware + CSS/JSON)
diagnostics found 1 warning (useOptionalChain) 0 0 0 (Biome non-JS: 0) prior JS warning fixed/migrated

Type-aware on vs off (Oxlint only): median 1.168 s vs 0.598 s (~2×). Decision: keep type-aware on in lint:js for floating-promise + cycle coverage; lint:js:no-type-aware available for local comparison.

Manual validation results

On Node 24.18.0 with pnpm 10.17.0 (current main toolchain):

pnpm install
pnpm format:check   # pass
pnpm lint           # pass (oxlint --type-aware: 0 warnings, 0 errors; biome non-JS: clean)
pnpm typecheck      # pass
pnpm test           # 305 passed
pnpm build          # pass

Known limitations

  • Combined lint is slower than Biome-only because type-aware Oxlint + Biome non-JS both run; JS semantic coverage is richer
  • jsx-a11y/prefer-tag-over-role intentionally off
  • CLI template tree ignored by Oxlint (same as not being a workspace consumer install)

Prohibited workarounds

Confirmed not used: --force, --legacy-peer-deps, dependency overrides/resolutions, patch-package, vendored forks, blanket TS/lint suppressions, prerelease packages, silent snapshot updates, unrelated features/UI churn.

Summary by CodeRabbit

  • Accessibility
    • Added/expanded aria-labels for slide setup-mode selectors, color inputs, asset selection/upload controls, and drop zones.
  • Bug Fixes
    • Improved rename/comment/editor submission reliability with safer async handling and clearer rename-failure feedback.
    • Refined asset loading/refresh behavior and a few interaction edge cases.
  • Documentation
    • Updated development workflow commands and pre-commit/CI checklists to reflect separate format vs lint steps.
  • Chores
    • Updated lint tooling and workspace scripts to use Oxlint for JS/TS (alongside existing formatting/non-JS checks).

cursoragent and others added 3 commits July 25, 2026 23:18
Adopt Oxlint 1.74 for JS/TS/React/Vitest/import semantics with
type-aware floating-promise and import-cycle checks. Keep Biome as the
sole formatter and non-JS linter (CSS/JSON). Update scripts, docs, and
fix correctness findings surfaced by the new lint path.

Co-authored-by: Davy <thedavidweng@users.noreply.github.com>
…icker-dialog

Add oxlint-enable after the drop-zone section so jsx-a11y/no-noninteractive-element-interactions
is not silenced for the rest of each file.
@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

@thedavidweng is attempting to deploy a commit to the open-slide Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The workspace now uses Oxlint for JavaScript and TypeScript linting and Biome for formatting and non-JavaScript linting. Configuration, documentation, accessibility labels, promise handling, hook dependencies, and related lint-driven code and test updates were added.

Changes

Linting split and remediation

Layer / File(s) Summary
Tooling configuration and workflow
.oxlintrc.json, biome.json, package.json, packages/core/tsconfig.json, .changeset/*, AGENTS.md, CONTRIBUTING.md, README.md
Workspace scripts and configuration define the Oxlint/Biome split; documentation describes tool responsibilities and the updated check commands.
Accessibility markup and error handling
apps/web/components/landing/*, packages/core/src/app/components/asset-view.tsx, packages/core/src/app/components/inspector/*, packages/core/src/app/components/style-panel/*, packages/core/src/app/routes/slide.tsx, packages/core/src/locale/*
Interactive controls receive accessible labels, drag-and-drop interactions use scoped suppressions, and rename/comment failures retain or reset editing state with localized feedback.
Promise handling and hook dependency updates
packages/core/src/app/components/*, packages/core/src/app/lib/*, packages/core/src/app/routes/*
Async event and effect calls explicitly discard promises, hook suppressions use Oxlint syntax, and asset refresh callbacks track current dependencies.
Remaining lint-driven cleanup
apps/demo/slides/*, apps/web/proxy.ts, packages/core/src/app/components/slide-canvas.tsx, packages/core/src/cli/*, packages/core/src/editing/*, packages/core/src/files/*, packages/core/src/vite/*
String iteration, proxy matcher setup, canvas styles, build configuration, identifier normalization, filename validation, and test assertions were updated.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: 1weiho

Poem

A bunny hops through linting light,
Oxlint checks the code just right.
Biome smooths each curly line,
Labels glow and promises align.
Clean little patches, carrots in sight! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% 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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: Oxlint handles JS/TS semantics while Biome remains for formatting and non-JS linting.

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (1)
packages/core/src/app/components/asset-view.tsx (1)

1384-1405: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid stale closures hidden by hook-dependency suppressions.

Both effects suppress missing dependencies while still reading values that can change during the mount.

  • packages/core/src/app/components/asset-view.tsx#L1384-L1405: t.asset.toastSearchFailed is locale-dependent and used inside the search error path; keep t’s relevant locale state/derived messages in dependencies or serialize a stable error code.
  • packages/core/src/app/components/sidebar/sidebar.tsx#L113-L126: commitCreate() still reads t, onCreate, and the folder icon state from its render closure, while the listener dependency includes only creating; keep t/onCreate updated via ref or dependencies, or use refs for the locale toast values in commitCreate().
🤖 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 `@packages/core/src/app/components/asset-view.tsx` around lines 1384 - 1405,
Update the effect around searchSvgl in
packages/core/src/app/components/asset-view.tsx:1384-1405 to avoid the
dependency suppression by tracking the locale-dependent
t.asset.toastSearchFailed value or a stable error code, while preserving the
existing search cancellation behavior. Update commitCreate and its listener in
packages/core/src/app/components/sidebar/sidebar.tsx:113-126 so changing t,
onCreate, or the folder icon state cannot leave the listener with stale
render-closure values; use appropriate dependencies or refs for these values.
🧹 Nitpick comments (1)
packages/core/src/app/components/inspector/asset-picker-dialog.tsx (1)

60-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Narrow the Oxlint suppression to the drop zone.

The disable begins before <Dialog> and remains active through the entire dialog, so it can suppress future accessibility violations on unrelated controls. Use the narrowest element- or next-line-scoped directive supported by Oxlint around the <section> drop zone only.

Also applies to: 191-191

🤖 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 `@packages/core/src/app/components/inspector/asset-picker-dialog.tsx` at line
60, Narrow the jsx-a11y/no-noninteractive-element-interactions suppression from
the file-level directive to the `<section>` drop zone in the asset picker
dialog. Use Oxlint’s element- or next-line-scoped directive immediately around
that section, and remove the broad suppression so unrelated `<Dialog>` controls
remain linted.
🤖 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 @.changeset/oxlint-biome-split.md:
- Line 5: Update the changeset description in oxlint-biome-split.md to be short,
direct, present-tense, and user-facing, describing the released `@open-slide/core`
improvements rather than internal Oxlint findings; mention editor accessibility
and async operation reliability.

In @.oxlintrc.json:
- Around line 41-46: Restrict the accessibility rule overrides in the .oxlintrc
configuration from the broad slides and landing directory globs to the specific
drop-zone component files that require them. Preserve the three disabled rules
for those targeted files while ensuring navigation and other landing components
remain subject to Oxlint accessibility checks.

In `@AGENTS.md`:
- Line 39: Update the validation guidance in AGENTS.md so pnpm check:fix is
described only as a preparatory formatting/lint-fix command, followed by pnpm
check for typechecking and final validation; do not present check:fix as an
alternative to pnpm check.
- Around line 27-29: Update the lint ownership documentation to match
biome.json’s disabled CSS linting: in AGENTS.md lines 27-29, CONTRIBUTING.md
line 56, and README.md line 91, replace CSS/JSON and non-JavaScript claims with
the actual JSON/non-JavaScript scope while preserving the existing command
descriptions.

In `@biome.json`:
- Around line 31-33: Update the JavaScript/TypeScript override in biome.json so
Biome’s security lint rules remain enabled, or configure equivalent Oxlint
security rules for the same file patterns. Ensure checks covering
dangerouslySetInnerHtml, dangerouslySetInnerHtmlWithChildren, and noGlobalEval
are preserved rather than disabled by the linter.enabled setting.

In `@packages/core/src/app/components/asset-view.tsx`:
- Around line 1135-1141: Handle promise rejections in RenameAsset.commit() and
InlineTitleEditor.commit() before setSaving(false), using the existing error
logging or retry behavior as appropriate. Apply the fix to both
commit-triggering handlers in packages/core/src/app/components/asset-view.tsx
(lines 1135-1141 and 1185-1191) and the corresponding handler in
packages/core/src/app/routes/slide.tsx (lines 1105-1112); each site must avoid
unhandled rejections from void commit() calls.

In `@packages/core/src/app/components/inspector/inspector-panel.tsx`:
- Line 933: Update the submit function in the inspector panel to catch and
handle errors from the awaited onAdd call, providing the existing user feedback
for failed submissions. Keep both keyboard and button handlers invoking submit
so all submission paths use the same rejection handling, and avoid discarding a
potentially rejected promise with only void submit().
- Around line 691-694: The wrapper labels do not name the native color controls.
Move aria-label={label} from each color-swatch wrapper onto its nested input
type="color" in
packages/core/src/app/components/inspector/inspector-panel.tsx#L706 and
packages/core/src/app/components/style-panel/style-panel.tsx#L240; remove it
from the wrappers while preserving their visual styling.

---

Outside diff comments:
In `@packages/core/src/app/components/asset-view.tsx`:
- Around line 1384-1405: Update the effect around searchSvgl in
packages/core/src/app/components/asset-view.tsx:1384-1405 to avoid the
dependency suppression by tracking the locale-dependent
t.asset.toastSearchFailed value or a stable error code, while preserving the
existing search cancellation behavior. Update commitCreate and its listener in
packages/core/src/app/components/sidebar/sidebar.tsx:113-126 so changing t,
onCreate, or the folder icon state cannot leave the listener with stale
render-closure values; use appropriate dependencies or refs for these values.

---

Nitpick comments:
In `@packages/core/src/app/components/inspector/asset-picker-dialog.tsx`:
- Line 60: Narrow the jsx-a11y/no-noninteractive-element-interactions
suppression from the file-level directive to the `<section>` drop zone in the
asset picker dialog. Use Oxlint’s element- or next-line-scoped directive
immediately around that section, and remove the broad suppression so unrelated
`<Dialog>` controls remain linted.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f6ee65be-eed2-4303-a21b-e74e2dc98d49

📥 Commits

Reviewing files that changed from the base of the PR and between c408ba0 and 6393682.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • .changeset/oxlint-biome-split.md
  • .oxlintrc.json
  • AGENTS.md
  • CONTRIBUTING.md
  • README.md
  • apps/demo/slides/open-slide-launch/index.tsx
  • apps/web/components/landing/hero-setup.tsx
  • apps/web/proxy.ts
  • biome.json
  • package.json
  • packages/core/src/app/components/asset-view.tsx
  • packages/core/src/app/components/inspector/asset-picker-dialog.tsx
  • packages/core/src/app/components/inspector/inspector-panel.tsx
  • packages/core/src/app/components/overview-grid.tsx
  • packages/core/src/app/components/sidebar/sidebar.tsx
  • packages/core/src/app/components/slide-canvas.tsx
  • packages/core/src/app/components/slide-preload-layer.tsx
  • packages/core/src/app/components/style-panel/style-panel.tsx
  • packages/core/src/app/lib/assets.ts
  • packages/core/src/app/lib/inspector/use-comments.ts
  • packages/core/src/app/lib/print-ready.test.ts
  • packages/core/src/app/routes/home-shell.tsx
  • packages/core/src/app/routes/home.tsx
  • packages/core/src/app/routes/presenter.tsx
  • packages/core/src/app/routes/slide.tsx
  • packages/core/src/cli/build.ts
  • packages/core/src/editing/edit-ops.ts
  • packages/core/src/files/assets.ts
  • packages/core/src/vite/design-plugin.test.ts
  • packages/core/tsconfig.json
💤 Files with no reviewable changes (1)
  • packages/core/tsconfig.json

Comment thread .changeset/oxlint-biome-split.md Outdated
Comment thread .oxlintrc.json
Comment on lines +41 to +46
"files": ["**/slides/**", "apps/web/components/landing/**"],
"rules": {
"jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"jsx-a11y/prefer-tag-over-role": "off"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== oxlintrc context =="
if [ -f .oxlintrc.json ]; then
  nl -ba .oxlintrc.json | sed -n '1,120p'
fi

echo
echo "== matching files count =="
# Use git ls-files to inspect tracked matching paths, not filesystem only.
git ls-files | awk '
  /\/slides\// {a++}
  /^apps\/web\/components\/landing\// {b++}
END {print "slides_tree:", a+0; print "landing_tree:", b+0}
'

echo
echo "== specific rule occurrences in matching tracked files =="
python3 - <<'PY'
import subprocess
import pathlib,textwrap

files = subprocess.check_output(["git","ls-files"], text=True).splitlines()
slides=[]
landing=[]
for f in files:
    if "/slides/" in f:
        slides.append(f)
    elif f.startswith("apps/web/components/landing/"):
        landing.append(f)
patterns=[
    'setAsDropZones',
    'dropZone',
    'isDropZone',
    'asset',
    'files',
    'onDrop',
    'onDragOver',
    'onDragEnter',
    'onClick',
    'interactions',
]
for p in patterns:
    hits=[]
    for f in slides+landing:
        try:
            data=pathlib.Path(f).read_text(encoding="utf-8", errors="ignore")
        except Exception:
            continue
        if p in data:
            hits.append(f"{f}:{data.count(p)}")
    if hits:
        print(f"\n[{p}] count={len(hits)}")
        for h in hits[:50]:
            print("  " + h)
        if len(hits)>50:
            print(f"  ... and {len(hits)-50} more")
PY

echo
echo "== direct usages outside matching trees =="
python3 - <<'PY'
import subprocess,pathlib
files = subprocess.check_output(["git","ls-files"], text=True).splitlines()
terms=['setAsDropZones','dropZone','isDropZone']
for term in terms:
    hits=[]
    for f in files:
        if pathlib.PurePath(f).is_absolute():
            continue
        try:
            data=pathlib.Path(f).read_text(encoding="utf-8", errors="ignore")
        except Exception:
            continue
        if term in data:
            hits.append(f)
    print(f"{term}: {len(hits)}")
    for h in hits[:80]:
        print(" ", h)
    if len(hits)>80:
        print(f" ... and {len(hits)-80} more")
PY

Repository: 1weiho/open-slide

Length of output: 218


🏁 Script executed:

#!/bin/bash
set -u

echo "== oxlintrc context =="
if [ -f .oxlintrc.json ]; then
  awk '{printf "%6d  %s\n", NR, $0}' .oxlintrc.json | sed -n '1,120p'
fi

echo
echo "== matching tracked files count =="
git ls-files | awk '
  /\/slides\// {a++}
  /^apps\/web\/components\/landing\// {b++}
END {print "slides_tree:", a+0; print "landing_tree:", b+0}
'

echo
echo "== relevant symbol occurrences in matching tracked files =="
python3 - <<'PY'
import subprocess, pathlib
files = subprocess.check_output(["git","ls-files"], text=True).splitlines()
slides=[f for f in files if "/slides/" in f]
landing=[f for f in files if f.startswith("apps/web/components/landing/")]
patterns=[
    'setAsDropZones',
    'dropZone',
    'isDropZone',
    'drop z',
    'onDrop',
    'onDragOver',
    'onDragEnter',
    'setDropZones',
]
for p in patterns:
    hits=[]
    for f in slides+landing:
        try:
            data=pathlib.Path(f).read_text(encoding="utf-8", errors="ignore")
        except Exception:
            continue
        if p in data:
            hits.append((f, data.count(p)))
    hits.sort(key=lambda x:x[1], reverse=True)
    if hits:
        print(f"\n[{p}] count={len(hits)}")
        for f, n in hits[:40]:
            print(f"  {f}: {n}")
        if len(hits)>40:
            print(f"  ... and {len(hits)-40} more")
PY

echo
echo "== targeted drop-zone symbol occurrences outside matching trees =="
python3 - <<'PY'
import subprocess, pathlib
files = subprocess.check_output(["git","ls-files"], text=True).splitlines()
terms=['setAsDropZones','dropZone','isDropZone','setDropZones']
matcher=lambda f: "/slides/" in f or f.startswith("apps/web/components/landing/")
for term in terms:
    in_tree=[]
    out_tree=[]
    for f in files:
        try:
            data=pathlib.Path(f).read_text(encoding="utf-8", errors="ignore")
        except Exception:
            continue
        if term in data:
            (out_tree if not matcher(f) else in_tree).append(f)
    print(f"{term}: in_tree={len(in_tree)}, out_tree={len(out_tree)}")
    for f in out_tree[:80]:
        print("  ", f)
PY

echo
echo "== rule occurrences outside .oxlintrc.json =="
if command -v rg >/dev/null 2>&1; then
 rg -n 'no-static-element-interactions|no-noninteractive-element-interactions|prefer-tag-over-role|files.*slides|components/landing' -g '!node_modules' . | sed -n '1,160p'
else
  grep -RInE 'no-static-element-interactions|no-noninteractive-element-interactions|prefer-tag-over-role|slide|landing' . | sed -n '1,160p'
fi

Repository: 1weiho/open-slide

Length of output: 7228


Narrow the accessibility override to the actual drop-zone files.

These tree-level overrides affect every tracked file under both patterns (apps/web/components/landing/** includes navigation and other landing components). Disable these rules only where the drop-zone exception is actually needed, e.g. specific component paths or targeted file names, so new inaccessible controls elsewhere aren’t excluded from Oxlint.

🤖 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 @.oxlintrc.json around lines 41 - 46, Restrict the accessibility rule
overrides in the .oxlintrc configuration from the broad slides and landing
directory globs to the specific drop-zone component files that require them.
Preserve the three disabled rules for those targeted files while ensuring
navigation and other landing components remain subject to Oxlint accessibility
checks.

Comment thread AGENTS.md
Comment on lines +27 to +29
pnpm lint # oxlint (JS/TS) + biome (CSS/JSON)
pnpm lint:js # oxlint only
pnpm lint:nonjs # biome non-JS lint only

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep CSS lint ownership documentation consistent with biome.json.

The final configuration disables Biome CSS linting, so these lines promise coverage that pnpm lint does not provide. Enable CSS linting or describe the actual JSON/non-JavaScript scope.

  • AGENTS.md#L27-L29: correct the biome (CSS/JSON) and non-JavaScript wording.
  • CONTRIBUTING.md#L56-L56: correct the CSS/JSON ownership description.
  • README.md#L91-L91: correct the CSS/JSON ownership description.
📍 Affects 3 files
  • AGENTS.md#L27-L29 (this comment)
  • CONTRIBUTING.md#L56-L56
  • README.md#L91-L91
🤖 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 `@AGENTS.md` around lines 27 - 29, Update the lint ownership documentation to
match biome.json’s disabled CSS linting: in AGENTS.md lines 27-29,
CONTRIBUTING.md line 56, and README.md line 91, replace CSS/JSON and
non-JavaScript claims with the actual JSON/non-JavaScript scope while preserving
the existing command descriptions.

Comment thread AGENTS.md
## Hard rules

- **Biome must pass before commit.** Run `pnpm check` (or `pnpm check:fix`). CI and the user's review both expect a clean tree.
- **Format and lint must pass before commit.** Run `pnpm check` (or `pnpm check:fix`). Oxlint owns JS/TS semantics; Biome owns formatting and non-JS lint. CI and the user's review both expect a clean tree.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not present check:fix as an alternative validation command.

check:fix runs fix commands but does not run typecheck or a final validation pass. Document it as a preparatory command, followed by pnpm check.

🤖 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 `@AGENTS.md` at line 39, Update the validation guidance in AGENTS.md so pnpm
check:fix is described only as a preparatory formatting/lint-fix command,
followed by pnpm check for typechecking and final validation; do not present
check:fix as an alternative to pnpm check.

Comment thread biome.json
Comment on lines +31 to +33
"includes": ["**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}", "**/*.d.ts"],
"linter": {
"rules": {
"a11y": {
"noSvgWithoutTitle": "off",
"noStaticElementInteractions": "off",
"useAriaPropsSupportedByRole": "off",
"useSemanticElements": "off"
},
"performance": {
"noImgElement": "off"
},
"complexity": {
"noUselessFragments": "off"
},
"security": {
"noDangerouslySetInnerHtml": "off"
},
"suspicious": {
"noArrayIndexKey": "off",
"noTemplateCurlyInString": "off",
"noShadowRestrictedNames": "off"
}
}
"enabled": false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching biome/oxlint/config scripts:\n'
git ls-files | rg '(^|/)(biome\.json|oxlint|oxlint\.json|eslint\.config|package\.json|pnpm-lock\.yaml|biome\.jsonc|\.oxlintrc)' || true

printf '\nbiome.json (if present):\n'
if [ -f biome.json ]; then
  nl -ba biome.json | sed -n '1,220p'
fi

printf '\nOxlint/config related files content snippets:\n'
for f in $(git ls-files | rg '(^|/)(oxlint|\.oxlintrc|eslint\.config|package\.json)$'); do
  printf '\n--- %s ---\n' "$f"
  nl -ba "$f" | sed -n '1,220p'
done

printf '\nSearch security/relevant rule names:\n'
rg -n "noDangerouslySetInnerHtml|noDangerouslySetInnerHtmlWithChildren|noGlobalEval|dangerouslySetInnerHTML|global.*eval|no.*eval|dangerous|oxlint|lint" -S . --glob '!node_modules' --glob '!dist' --glob '!build' | sed -n '1,240p'

Repository: 1weiho/open-slide

Length of output: 524


🌐 Web query:

Biome noDangerouslySetInnerHtml noGlobalEval rules security dangerous HTML eval

💡 Result:

Biome provides several security-focused lint rules designed to prevent vulnerabilities related to arbitrary code execution and Cross-Site Scripting (XSS), including the following: noDangerouslySetInnerHtml: This rule (lint/security/noDangerouslySetInnerHtml) prevents the use of the dangerouslySetInnerHTML prop in React, which can expose users to XSS attacks by injecting unescaped HTML [1][2]. It is enabled by default and set to error severity [1]. noGlobalEval: This rule (lint/security/noGlobalEval) prohibits the use of global eval, which evaluates strings as JavaScript code [3]. This is considered a security risk because it allows for arbitrary code execution if the input is untrusted, and it also negatively impacts performance by preventing JavaScript engine optimizations [3]. Additional related security rules include: - noDangerouslySetInnerHtmlWithChildren: Reports when an element uses both children and dangerouslySetInnerHTML simultaneously [4][5]. - noScriptUrl: Disallows the use of javascript: URLs in HTML (e.g., in href attributes), as they act as a form of eval and can lead to XSS [6]. - noImpliedEval: A nursery rule (currently unstable) that flags eval-like methods such as using setTimeout, setInterval, or the Function constructor with string arguments [7]. These rules are part of Biome's focus on identifying patterns that lead to security vulnerabilities and performance issues, favoring safer alternatives like direct function references or standard JSX rendering [1][3][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'biome.json excerpt:\n'
awk '{printf "%6d\t%s\n", NR, $0}' biome.json | sed -n '1,120p'

printf '\n.oxlintrc.json:\n'
awk '{printf "%6d\t%s\n", NR, $0}' .oxlintrc.json | sed -n '1,220p'

printf '\nRelevant rule/config mentions:\n'
for f in biometxt=".biome.json" || true; do
  true
done
rg -n "noDangerouslySetInnerHtml|noDangerouslySetInnerHtmlWithChildren|noGlobalEval|noUndocumentedGlobals|dangerouslySetInnerHTML|dangerous|eval|allowComments|noScriptUrl|noImpliedEval|noDangerous|dangerous" -S .biome.json biome.json .oxlintrc.json package.json 2>/dev/null || true

printf '\nPackage scripts:\n'
python3 - <<'PY'
import json, pathlib
for p in ["package.json", ".oxlintrc.json", "biome.json"]:
    if pathlib.Path(p).exists():
        print(f"\n--- {p} ---")
        data=json.loads(pathlib.Path(p).read_text())
        if p=="package.json":
            print(json.dumps({"scripts": data.get("scripts", {})}, indent=2)[:2000])
        else:
            print(json.dumps(data, indent=2)[:3000])
PY

Repository: 1weiho/open-slide

Length of output: 3299


Preserve Biome’s JS/TS security checks or add equivalent Oxlint rules.

The JS/TS override disables Biome security rules such as lint/security/noDangerouslySetInnerHtml, lint/security/noDangerouslySetInnerHtmlWithChildren, and lint/security/noGlobalEval for all JavaScript/TypeScript files. The Oxlint config does not enable equivalent security rules, so this split removes the existing XSS/code-execution diagnostics.

🤖 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 `@biome.json` around lines 31 - 33, Update the JavaScript/TypeScript override
in biome.json so Biome’s security lint rules remain enabled, or configure
equivalent Oxlint security rules for the same file patterns. Ensure checks
covering dangerouslySetInnerHtml, dangerouslySetInnerHtmlWithChildren, and
noGlobalEval are preserved rather than disabled by the linter.enabled setting.

Comment on lines +1135 to +1141
if (!saving) void commit();
}}
onKeyDown={(event) => {
if (event.nativeEvent.isComposing) return;
if (event.key === 'Enter') {
event.preventDefault();
commit();
void commit();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate files:"
git ls-files | rg 'packages/core/src/app/(components/asset-view\.tsx|routes/slide\.tsx)$' || true

echo
echo "Relevant asset-view lines:"
sed -n '1110,1200p' packages/core/src/app/components/asset-view.tsx

echo
echo "Relevant slide lines:"
sed -n '1085,1120p' packages/core/src/app/routes/slide.tsx

echo
echo "Search commit definitions/callback usages:"
rg -n "function .*commit|const .*commit|commit=\(|commit=\(\)|onBlur=|onKeyDown|Promise|reject|Error|Async" packages/core/src/app -g '*.tsx' -g '*.ts' | head -n 300

Repository: 1weiho/open-slide

Length of output: 27297


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "asset-view rename component definition:"
sed -n '1030,1105p' packages/core/src/app/components/asset-view.tsx
sed -n '1100,1135p' packages/core/src/app/components/asset-view.tsx

echo
echo "slide rename component definition:"
sed -n '1030,1105p' packages/core/src/app/routes/slide.tsx
sed -n '1100,1115p' packages/core/src/app/routes/slide.tsx

echo
echo "Component callers for RenameEditable:"
rg -n "<RenameEditable|RenameEditable<|onSubmit=\\(" packages/core/src/app -g '*.tsx' -g '*.ts' -C 3

echo
echo "Read-only behavioral probe: unhandled async rejection logged by Node when using void"
node - <<'JS'
let logged;
process.on('unhandledRejection', (reason, promise) => {
  logged = String(reason);
});
async function reject() {
  throw new Error('rename failed');
}
(async () => {
  void reject();
  await new Promise(resolve => setTimeout(resolve, 20)).then(() => {
    console.log(logged);
  });
})();
JS

Repository: 1weiho/open-slide

Length of output: 5712


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find RenameEditable declarations/usages by regex across tracked files:"
rg -n "function RenameEditable|const Rename|class Rename|type Rename|interface Rename|<RenameEditable|RenameEditable<|onSubmit=\(next|" packages/core/src -g '*.tsx' -g '*.ts' -C 4 || true

echo
echo "Surrounding RenameAsset usages:"
rg -n "RenameAsset<" packages/core/src/app -g '*.tsx' -g '*.ts' -C 8 || true

echo
echo "Exact rename call sites containing toast/error handling with await onSubmit or promise rejection:"
rg -n "await onSubmit|onSubmit\\\\(|toast|toast\\.error|\\.catch|unhandled" packages/core/src/app -g '*.tsx' -g '*.ts' -C 4 || true

echo
echo "Read-only behavioral probe: unhandled async rejection logged by Node when using void"
node - <<'JS'
let logged = null;
process.on('unhandledRejection', (reason, promise) => {
  logged = String(reason);
});
(async () => {
  void Promise.reject(new Error('rename failed'));
  await new Promise(resolve => setTimeout(resolve, 20));
  console.log(logged);
})();
JS

Repository: 1weiho/open-slide

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "RenameAsset declarations/usages in app subdir only (limited):"
rg -n "function RenameAsset|<RenameAsset|onSubmit=\(" packages/core/src/app -g '*.tsx' -C 6 || true

echo
echo "Renamed asset toast/error call sites in app/assets.ts:"
sed -n '1,80p' packages/core/src/app/lib/assets.ts
rg -n "renameAsset|toast\.error|toastRename|rename\(" packages/core/src/app lib packages/core/src -g '*.tsx' -g '*.ts' -C 4 || true

echo
echo "Slide commit caller context around InlineTitleEditor if present:"
rg -n "InlineTitleEditor|renameSlide|toastSlide|toast.*rename|toast.*failed" packages/core/src/app/routes/slide.tsx -C 8 || true

echo
echo "Focused text matches around RenameEditable in all tracked files:"
rg -n "RenameEditable|onSubmit=\(\w+\)" packages/core/src -g '*.tsx' -g '*.ts' --glob '!node_modules' -C 4 | head -n 200 || true

Repository: 1weiho/open-slide

Length of output: 50373


Handle rename promise rejections at each commit boundary.

These handlers return promises to a commit() that is fired with void, so failures can surface as unhandled rejections. Catch/reject-log/retry in RenameAsset.commit() and InlineTitleEditor.commit() before setSaving(false).

📍 Affects 2 files
  • packages/core/src/app/components/asset-view.tsx#L1135-L1141 (this comment)
  • packages/core/src/app/components/asset-view.tsx#L1185-L1191
  • packages/core/src/app/routes/slide.tsx#L1105-L1112
🤖 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 `@packages/core/src/app/components/asset-view.tsx` around lines 1135 - 1141,
Handle promise rejections in RenameAsset.commit() and InlineTitleEditor.commit()
before setSaving(false), using the existing error logging or retry behavior as
appropriate. Apply the fix to both commit-triggering handlers in
packages/core/src/app/components/asset-view.tsx (lines 1135-1141 and 1185-1191)
and the corresponding handler in packages/core/src/app/routes/slide.tsx (lines
1105-1112); each site must avoid unhandled rejections from void commit() calls.

Comment thread packages/core/src/app/components/inspector/inspector-panel.tsx
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
submit();
void submit();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle rejected submissions before discarding the promise.

void submit() only satisfies the no-floating-promises rule. submit awaits onAdd, and the supplied useComments.add throws on failed POST requests, so keyboard submission can produce an unhandled rejection with no user feedback. Handle the error inside submit so both keyboard and button submissions are covered.

🤖 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 `@packages/core/src/app/components/inspector/inspector-panel.tsx` at line 933,
Update the submit function in the inspector panel to catch and handle errors
from the awaited onAdd call, providing the existing user feedback for failed
submissions. Keep both keyboard and button handlers invoking submit so all
submission paths use the same rejection handling, and avoid discarding a
potentially rejected promise with only void submit().

…abbit

- Catch promise rejections in RenameAsset.commit, InlineTitleEditor.commit,
  and inspector comment submit to avoid unhandled rejections.
- Move aria-label from color-swatch wrapper <label> onto the native
  <input type="color"> in inspector-panel and style-panel.
- Narrow the jsx-a11y/no-noninteractive-element-interactions suppression in
  asset-picker-dialog from file-level to the drop-zone <section> only.
- Tighten the oxlint changeset description.
@thedavidweng

Copy link
Copy Markdown
Author

Addressed CodeRabbit review — pushed fixes for the actionable items:

Fixed:

  • Promise rejection handling in RenameAsset.commit, InlineTitleEditor.commit, and inspector comment submit — added catch blocks to avoid unhandled rejections.
  • aria-label moved from the color-swatch wrapper <label> onto the native <input type="color"> in inspector-panel.tsx and style-panel.tsx.
  • Narrowed the jsx-a11y/no-noninteractive-element-interactions suppression in asset-picker-dialog.tsx from file-level to the drop-zone <section> only.
  • Tightened the oxlint changeset description.

Skipped (with rationale):

  • .oxlintrc.json a11y override globs (**/slides/**, apps/web/components/landing/**): These are intentional. Slide content is arbitrary user-authored React that routinely uses non-interactive elements for custom interactions (the framework's core use case). Narrowing to specific files would require enumerating every slide file, which is not feasible. The landing page has multiple custom-interaction sections. This is a design decision, not an oversight.

  • biome.json security rules (dangerouslySetInnerHTML etc.): The JS/TS lint override disables all Biome JS linting because Oxlint owns JS/TS semantics. Oxlint has no-eval but not noDangerouslySetInnerHTML. Re-enabling Biome security rules for JS/TS would cause double-linting with Oxlint. The dangerouslySetInnerHTML usage in this codebase is intentional and limited. This is a documented trade-off.

  • asset-view.tsx 1384-1405 and sidebar.tsx 113-126 (stale closures): Both already have oxlint-disable-line with explanatory comments. The asset-view effect uses retryToken as a bump-to-refetch trigger and the toast string is locale-stable. The sidebar commitCreate reads latest state via stateRef. These suppressions are intentional and documented.

  • AGENTS.md lint ownership docs: The current description ("Biome owns formatting and non-JS lint") is accurate — Biome's linter.enabled: true with recommended: true lints CSS/JSON/etc., while the JS/TS override disables Biome JS lint in favor of Oxlint. CSS lint is NOT disabled (it's the non-JS scope Biome owns). No change needed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/app/routes/slide.tsx`:
- Around line 1079-1081: Update the catch block in InlineTitleEditor to display
a localized error toast when renameSlide fails, matching the existing asset
rename error-toast pattern in asset-view.tsx. Preserve the current
setValue(title) and setEditing(false) cleanup behavior.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0e47992b-80f1-4aa7-8d64-a10439f43342

📥 Commits

Reviewing files that changed from the base of the PR and between 6393682 and 82180bc.

📒 Files selected for processing (6)
  • .changeset/oxlint-biome-split.md
  • packages/core/src/app/components/asset-view.tsx
  • packages/core/src/app/components/inspector/asset-picker-dialog.tsx
  • packages/core/src/app/components/inspector/inspector-panel.tsx
  • packages/core/src/app/components/style-panel/style-panel.tsx
  • packages/core/src/app/routes/slide.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/src/app/components/inspector/asset-picker-dialog.tsx
  • packages/core/src/app/components/inspector/inspector-panel.tsx

Comment thread packages/core/src/app/routes/slide.tsx
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.

2 participants