Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/oxlint-biome-split.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@open-slide/core": patch
---

Fix lint findings uncovered by Oxlint (floating promises, hook deps, a11y labels).
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
49 changes: 49 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "unicorn", "react", "import", "vitest", "jsx-a11y"],
"categories": {
"correctness": "error"
},
"env": {
"builtin": true
},
"settings": {
"react": {
"version": "19.2.7"
}
},
"ignorePatterns": [
"**/dist/**",
"**/node_modules/**",
"**/*.tsbuildinfo",
"packages/core/src/app/components/ui/**",
"packages/cli/template/**"
],
"rules": {
"import/no-cycle": "error",
"typescript/no-floating-promises": "error",
"jsx-a11y/prefer-tag-over-role": "off",
"vitest/expect-expect": [
"error",
{
"assertFunctionNames": [
"expect",
"expectTypeOf",
"assert",
"expectTagged",
"expectTaggedTransform"
]
}
]
},
"overrides": [
{
"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"
}
Comment on lines +41 to +46

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.

}
]
}
10 changes: 7 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,20 @@ Shared config: `biome.json`, `turbo.json`, `pnpm-workspace.yaml`, `tsconfig` per
pnpm dev # turbo: runs demo against local core
pnpm build # build all packages
pnpm typecheck # tsc across the graph
pnpm check # biome (format + lint + organize imports)
pnpm check:fix # auto-fix what biome can
pnpm format:check # biome formatter
pnpm lint # oxlint (JS/TS) + biome (CSS/JSON)
pnpm lint:js # oxlint only
pnpm lint:nonjs # biome non-JS lint only
Comment on lines +27 to +29

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.

pnpm check # format:check + lint + typecheck
pnpm check:fix # oxlint --fix + biome check --write
pnpm test # vitest
```

Filter to one package: `pnpm core <script>` / `pnpm cli <script>`.

## 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.

- **If `packages/core` or `packages/cli` changes, add a changeset.** Run `pnpm changeset`, pick the right package(s) and bump (`patch` for fixes/polish, `minor` for new public API, `major` for breaking). Apps (`demo`, `web`) and root tooling do **not** need one.
- **Changeset descriptions: short and direct.** One line, present-tense, what changed from a user's perspective. Match the tone of `.changeset/*.md` already in the repo. No paragraphs, no rationale, no "this PR…".
- Good: `Replace spinner with a hairline + sliding bar for slide and presenter loading states.`
Expand Down
13 changes: 7 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ pnpm dev
pnpm dev # turbo: runs demo against local core
pnpm build # build all packages
pnpm typecheck # tsc across the graph
pnpm check # biome (format + lint + organize imports)
pnpm check:fix # auto-fix what biome can
pnpm format:check # biome formatter
pnpm lint # oxlint (JS/TS) + biome (CSS/JSON)
pnpm check # format:check + lint + typecheck
pnpm check:fix # oxlint --fix + biome check --write
pnpm test # vitest
```

Expand All @@ -70,8 +72,7 @@ pnpm cli <script>
2. **Make your change.** Match the surrounding style. Don't reformat unrelated code.
3. **Run the checks before pushing:**
```bash
pnpm check # must pass — CI enforces it
pnpm typecheck
pnpm check # must pass — CI enforces format + lint + typecheck
pnpm test
```
`pnpm check:fix` will auto-fix most formatting and lint issues.
Expand All @@ -98,10 +99,10 @@ pnpm cli <script>

## Style & conventions

- **Biome must pass.** Formatting, lint, and import organisation are all enforced by `pnpm check`.
- **Format and lint must pass.** Formatting and non-JS lint are enforced by Biome; JS/TS semantic lint is enforced by Oxlint. Use `pnpm check`.
- **No casual dependencies.** The `core` runtime ships to users — every dep inflates install size. Prefer a small piece of inline code over a new package.
- **Default to writing no comments.** Only add one when the *why* is non-obvious — a hidden constraint, a subtle invariant, a workaround for a specific bug. Don't explain *what* the code does; well-named identifiers handle that.
- **Leave `packages/core/src/app/components/ui` alone.** It's shadcn-generated and biome-ignored unless you're regenerating it.
- **Leave `packages/core/src/app/components/ui` alone.** It's shadcn-generated and ignored by Biome/Oxlint unless you're regenerating it.

## Testing

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ This repo is a pnpm + Turbo monorepo.
pnpm install
pnpm dev # runs the demo against the local @open-slide/core
pnpm build # builds all packages
pnpm check # type-checks all packages
pnpm lint # lints via biome
pnpm check # format:check + lint + typecheck
pnpm lint # oxlint (JS/TS) + biome (CSS/JSON)
```

## Star history
Expand Down
2 changes: 1 addition & 1 deletion apps/demo/slides/open-slide-launch/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const Letters = ({
style?: CSSProperties;
}) => (
<span className={className} style={{ display: 'inline-flex', whiteSpace: 'pre', ...style }}>
{[...text].map((c, i) => (
{Array.from(text).map((c, i) => (
<span
key={i}
style={{
Expand Down
1 change: 1 addition & 0 deletions apps/web/components/landing/hero-setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export function HeroSetup() {
) : null}
<button
type="button"
aria-label={setupOptions[key].label}
aria-pressed={mode === key}
onClick={() => selectMode(key)}
className={`pressable rounded-md py-1.5 ${
Expand Down
8 changes: 4 additions & 4 deletions apps/web/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@ import { isMarkdownPreferred, rewritePath } from 'fumadocs-core/negotiation';
import { type NextRequest, NextResponse } from 'next/server';
import { docsContentRoute, docsRoute } from '@/lib/shared';

const { rewrite: rewriteDocs } = rewritePath(
const docsPathRewrite = rewritePath(
`${docsRoute}{/*path}`,
`${docsContentRoute}{/*path}/content.md`,
);
const { rewrite: rewriteSuffix } = rewritePath(
const suffixPathRewrite = rewritePath(
`${docsRoute}{/*path}.mdx`,
`${docsContentRoute}{/*path}/content.md`,
);

export default function proxy(request: NextRequest) {
const result = rewriteSuffix(request.nextUrl.pathname);
const result = suffixPathRewrite.rewrite(request.nextUrl.pathname);
if (result) {
return NextResponse.rewrite(new URL(result, request.nextUrl));
}

if (isMarkdownPreferred(request)) {
const result = rewriteDocs(request.nextUrl.pathname);
const result = docsPathRewrite.rewrite(request.nextUrl.pathname);

if (result) {
return NextResponse.rewrite(new URL(result, request.nextUrl));
Expand Down
25 changes: 2 additions & 23 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,9 @@
},
"overrides": [
{
"includes": ["**/slides/**", "apps/web/components/landing/**"],
"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
Comment on lines +31 to +33

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.

}
}
],
Expand Down
13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
"typecheck": "turbo run typecheck",
"format": "biome format --write .",
"format:check": "biome format .",
"lint": "biome lint .",
"lint:fix": "biome lint --write .",
"check": "biome check .",
"check:fix": "biome check --write .",
"lint:js": "oxlint --type-aware --deny-warnings",
"lint:nonjs": "biome lint .",
"lint": "pnpm lint:js && pnpm lint:nonjs",
"lint:fix": "oxlint --type-aware --fix && biome lint --write .",
"check": "pnpm format:check && pnpm lint && pnpm typecheck",
"check:fix": "oxlint --type-aware --fix && biome check --write .",
"lint:js:no-type-aware": "oxlint --deny-warnings",
"test": "vitest run",
"test:watch": "vitest",
"test:e2e": "pnpm --filter @open-slide/core test:e2e",
Expand All @@ -28,6 +31,8 @@
"@biomejs/biome": "2.4.12",
"@changesets/changelog-github": "^0.7.0",
"@changesets/cli": "^2.31.0",
"oxlint": "1.74.0",
"oxlint-tsgolint": "0.24.0",
"turbo": "^2.10.5",
"vitest": "^2.1.9"
}
Expand Down
14 changes: 8 additions & 6 deletions packages/core/src/app/components/asset-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ export function AssetView({ slideId }: Props) {
);
}

/* oxlint-disable jsx-a11y/no-noninteractive-element-interactions -- file drop zone */
return (
<section
aria-label={t.asset.sectionAria}
Expand Down Expand Up @@ -626,6 +627,7 @@ export function AssetView({ slideId }: Props) {
</section>
);
}
/* oxlint-enable jsx-a11y/no-noninteractive-element-interactions */

function EmptyState() {
const t = useLocale();
Expand Down Expand Up @@ -1130,13 +1132,13 @@ function RenameAsset({
disabled={saving}
onChange={(event) => setValue(event.target.value)}
onBlur={() => {
if (!saving) commit();
if (!saving) void commit();
}}
onKeyDown={(event) => {
if (event.nativeEvent.isComposing) return;
if (event.key === 'Enter') {
event.preventDefault();
commit();
void commit();
Comment on lines +1138 to +1144

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.

} else if (event.key === 'Escape') {
event.preventDefault();
onCancel();
Expand Down Expand Up @@ -1180,13 +1182,13 @@ function RenameAsset({
disabled={saving}
onChange={(e) => setValue(e.target.value)}
onBlur={() => {
if (!saving) commit();
if (!saving) void commit();
}}
onKeyDown={(e) => {
if (e.nativeEvent.isComposing) return;
if (e.key === 'Enter') {
e.preventDefault();
commit();
void commit();
} else if (e.key === 'Escape') {
e.preventDefault();
onCancel();
Expand Down Expand Up @@ -1379,7 +1381,7 @@ function LogoSearchDialog({
queueMicrotask(() => inputRef.current?.focus());
}, []);

// biome-ignore lint/correctness/useExhaustiveDependencies: retryToken is a bump-to-refetch trigger
// retryToken is a bump-to-refetch trigger; toast copy is locale-stable
useEffect(() => {
const ctrl = new AbortController();
const timer = setTimeout(() => {
Expand All @@ -1400,7 +1402,7 @@ function LogoSearchDialog({
clearTimeout(timer);
ctrl.abort();
};
}, [query, retryToken]);
}, [query, retryToken]); // oxlint-disable-line react-hooks/exhaustive-deps -- toast string is locale-stable

return (
<Dialog open onOpenChange={(open) => !open && onClose()}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export function AssetPickerDialog({
[effectiveSlideId, scope, refresh, onPick, t],
);

/* oxlint-disable jsx-a11y/no-noninteractive-element-interactions -- file drop zone */
return (
<Dialog open onOpenChange={(o) => !o && onClose()}>
<DialogContent className="sm:max-w-xl">
Expand Down Expand Up @@ -93,6 +94,7 @@ export function AssetPickerDialog({
id={inputId}
type="file"
accept="image/*"
aria-label={t.asset.upload}
className="sr-only"
disabled={uploading}
onChange={(e) => {
Expand Down Expand Up @@ -142,6 +144,7 @@ export function AssetPickerDialog({
<button
key={asset.name}
type="button"
aria-label={asset.name}
onClick={() => onPick(asset, scope)}
className={cn(
'group flex flex-col overflow-hidden rounded-lg border bg-card text-left shadow-sm transition-all',
Expand Down Expand Up @@ -185,6 +188,7 @@ export function AssetPickerDialog({
</Dialog>
);
}
/* oxlint-enable jsx-a11y/no-noninteractive-element-interactions */

function hasFiles(e: React.DragEvent): boolean {
const types = e.dataTransfer?.types;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,10 @@ function ColorField({

return (
<Field label={label}>
<label className="relative inline-flex size-8 shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-md border bg-background shadow-xs">
<label
aria-label={label}
className="relative inline-flex size-8 shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-md border bg-background shadow-xs"
>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<span
className="size-5 rounded-sm"
style={{
Expand Down Expand Up @@ -927,7 +930,7 @@ function CommentsSection({
onKeyDown={(e) => {
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().

}
}}
placeholder={t.inspector.commentPlaceholder}
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/app/components/overview-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ export function OverviewGrid({
const focusedRef = useRef<HTMLButtonElement | null>(null);
const t = useLocale();

// biome-ignore lint/correctness/useExhaustiveDependencies: only re-sync on open transition
// only re-sync focused index on open transition
useEffect(() => {
if (open) setFocused(current);
}, [open]);
}, [open]); // oxlint-disable-line react-hooks/exhaustive-deps -- intentionally ignore `current` while open

// biome-ignore lint/correctness/useExhaustiveDependencies: `focused` swaps which button holds the ref; we must re-run to focus the new node
useEffect(() => {
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/app/components/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export function Sidebar({
}
};

// biome-ignore lint/correctness/useExhaustiveDependencies: commitCreate reads latest state via stateRef
// commitCreate reads latest state via stateRef
useEffect(() => {
if (!creating) return;
const onDown = (e: MouseEvent) => {
Expand All @@ -119,11 +119,11 @@ export function Sidebar({
if (!target) return;
if (target.closest('[data-folder-create]')) return;
if (target.closest('[data-slot="popover-content"]')) return;
commitCreate();
void commitCreate();
};
document.addEventListener('mousedown', onDown);
return () => document.removeEventListener('mousedown', onDown);
}, [creating]);
}, [creating]); // oxlint-disable-line react-hooks/exhaustive-deps -- commitCreate via stateRef

return (
<aside className="relative flex h-full w-[16.5rem] shrink-0 flex-col border-r border-hairline bg-sidebar text-sidebar-foreground">
Expand Down Expand Up @@ -270,7 +270,7 @@ export function Sidebar({
onChange={(e) => setNewName(e.target.value)}
onKeyDown={(e) => {
if (e.nativeEvent.isComposing) return;
if (e.key === 'Enter') commitCreate();
if (e.key === 'Enter') void commitCreate();
if (e.key === 'Escape') exitCreate();
}}
placeholder={t.home.folderName}
Expand Down
Loading