Skip to content

enable React Compiler and add Oxc tooling - #26

Open
Priyansh4444 wants to merge 2 commits into
pc-style:betafrom
Priyansh4444:feat/react-compiler-oxc-tooling
Open

enable React Compiler and add Oxc tooling#26
Priyansh4444 wants to merge 2 commits into
pc-style:betafrom
Priyansh4444:feat/react-compiler-oxc-tooling

Conversation

@Priyansh4444

@Priyansh4444 Priyansh4444 commented Aug 5, 2026

Copy link
Copy Markdown

Summary

  • Upgrade React to 19.2.8 and Vite to 8.2.0.
  • Enable React Compiler through the official Vite React plugin preset.
  • Remove all executable-source useMemo, useCallback, and memo usage so the compiler owns memoization.
  • Add React 19 action patterns with useActionState, useFormStatus, and useOptimistic to reply, post-edit, space creation, profile save, and agent-task flows.
  • Add restrained pending/optimistic transitions with reduced-motion support.
  • Add Oxlint 1.77.0 and Oxfmt 0.62.0 with repository scripts and configuration.
  • Format the configured source, Convex, script, and test files with Oxfmt.
  • Resolve Oxlint diagnostics without lint-disable suppressions.
  • Add a hook-safe rich embed render seam for direct component tests.
  • Rerun React Scan and preserve the existing dev-only initialization without adding production CDN instrumentation.

Testing

  • bun run format:check
  • bun run lint
  • bun run test
  • bun run typecheck
  • bun run build
  • bunx react-scan init --yes --skip-install

All checks pass: Oxfmt, Oxlint with zero diagnostics, 112 unit tests, 92 Convex tests, typecheck, and production build.

@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

@Priyansh4444 is attempting to deploy a commit to the pcstyle Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: adabdd17-f701-4ebf-8ad1-c623c995b1b5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@socket-security

socket-security Bot commented Aug 5, 2026

Copy link
Copy Markdown

@Priyansh4444

Copy link
Copy Markdown
Author

Will be a huge pr if considering oxc tooling.
I should have split it into two, but if you can get through this along with adding jasmine testing; this would be kinda good to run 👍

@cubic-dev-ai cubic-dev-ai 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.

14 issues found across 155 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/flashExperiments/ExperimentDiscussion.tsx">

<violation number="1" location="src/flashExperiments/ExperimentDiscussion.tsx:264">
P2: Opening a nested reply composer does not focus its textarea because `focusBodyOnMount` is enabled without supplying the ref that `ComposerShell` uses for focusing. Wiring a `useRef<HTMLTextAreaElement>` through this local `Composer` as `textareaRef` would restore the intended reply flow.</violation>
</file>

<file name="src/components/AttachmentGallery.tsx">

<violation number="1" location="src/components/AttachmentGallery.tsx:40">
P2: The new `<track kind="captions" src="data:text/vtt,WEBVTT" />` advertises captions that don't exist. It points at an empty WebVTT file (zero cues), so users who enable the CC control get nothing, and since it isn't marked `default` it's never even selected at runtime. If the goal was to genuinely support captions, ship a real caption source (with `srcLang`/`label`); if captions aren't available, drop the track rather than claiming caption availability that the video doesn't deliver — an empty captions track is an accessibility anti-pattern that misrepresents the video's accessibility.</violation>
</file>

<file name="scripts/linear-pull.mjs">

<violation number="1" location="scripts/linear-pull.mjs:147">
P3: The comment `// Build issue lookup for dependency resolution` now sits above nothing — the `issueMap` it described was removed in this diff, so it's orphaned and misleading. Drop the stale comment line.</violation>
</file>

<file name="src/routes/redesign/RedesignPostPage.tsx">

<violation number="1" location="src/routes/redesign/RedesignPostPage.tsx:253">
P3: Sidebar resizing no longer exposes current width or bounds to assistive technology: this button only announces a label while ArrowLeft/ArrowRight change the value. Restore a resize/slider semantic with value and range ARIA properties rather than presenting it as an action button.</violation>
</file>

<file name="src/components/PostForm.tsx">

<violation number="1" location="src/components/PostForm.tsx:246">
P2: Wall-post dialog now opens with focus on its close button instead of the title field because this no longer renders the `autofocus` attribute Dialog uses for fallback focus. Preserve an autofocus marker or provide the wall dialog's title ref to `Dialog.initialFocusRef`.</violation>
</file>

<file name="src/components/ComposerShell.tsx">

<violation number="1" location="src/components/ComposerShell.tsx:119">
P2: The Cmd/Ctrl+Enter fallback can insert a newline into the textarea because this branch requests the form submission without preventing the original key event; the callback branch already suppresses it. Prevent the default event before calling `requestSubmit()` so the keyboard shortcut submits without editing the draft.</violation>
</file>

<file name="src/components/Composer.tsx">

<violation number="1" location="src/components/Composer.tsx:131">
P2: After a failed reply, editing the draft leaves the old error alert visible. The direct setter removed the previous on-change reset, so the submission error should be cleared when the draft changes.</violation>

<violation number="2" location="src/components/Composer.tsx:193">
P1: Pressing Cmd/Ctrl+Enter repeatedly while a reply is sending can create duplicate replies and agent tasks. The native form path bypasses the old `busy` guard, so the shortcut should be prevented while `isSubmitting` or the action should have an equivalent submission guard.</violation>
</file>

<file name="src/components/RichEmbedList.tsx">

<violation number="1" location="src/components/RichEmbedList.tsx:59">
P3: Every rich-embed render now reparses and rebuilds the body’s previews in addition to the wrapper’s memoized calculation, and the duplicated logic can drift later. A shared `buildPreviews(text)` helper or passing the computed previews into `RichEmbedListView` would keep one derivation and avoid the extra scan.</violation>

<violation number="2" location="src/components/RichEmbedList.tsx:110">
P2: Direct video previews advertise a captions track that can never display any captions, so users who need captions still have no accessible equivalent for videos with speech. Real per-video WebVTT should be supplied when available; otherwise this empty track should not be treated as caption support.</violation>
</file>

<file name="package.json">

<violation number="1" location="package.json:53">
P3: The new @types/babel__core (^7.20.5) is bumped a major behind the added @babel/core (^8.0.1); Babel 8 has no matching ⁄types release, and nothing in the source imports either package (only @rolldown/plugin-babel, which brings its own types). Consider dropping the @types/babel__core devDependency so the types don't mislead against the v8 runtime.</violation>
</file>

<file name="src/components/UserSwitcher.tsx">

<violation number="1" location="src/components/UserSwitcher.tsx:46">
P2: Pressing Escape closes the switcher but leaves focus on the unmounted selected-user button, typically dropping focus to `body` instead of returning it to the trigger. The new `<dialog>` removed the only Escape path that called `triggerRef.current?.focus()`; retain that restoration for keyboard dismissal.</violation>
</file>

<file name="convex/orgs.test.ts">

<violation number="1" location="convex/orgs.test.ts:30">
P3: The removed second argument to `expect(...)` was a per-iteration diagnostic message. Now if any slug in the loop fails (`ab`, `-acme`, `acme-`, `Acme`, `acme_team`, `postwork`, `staging`), the assertion failure no longer names which slug caused it, so debugging a regression requires re-running with added logging. Consider keeping a custom message (e.g. `expect(orgSlugError(slug), slug).not.toBeNull()`) or otherwise including the slug in the assertion output.</violation>
</file>

<file name="src/components/Skeleton.tsx">

<violation number="1" location="src/components/Skeleton.tsx:13">
P3: `<output>` cannot contain a block-level `<div>` (its content model is phrasing content), so nesting the skeleton `<div aria-hidden>` grid inside it is invalid HTML. Since the original intent was an announcer (role="status"), either keep `role="status"` on a block element or split the layout: put the text in the `<output>` and the grid in a separate flow element outside it.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

onSubmit={() => void submit()}
submitting={isSubmitting}
disabled={isSubmitting || !body.trim() || hasUploading || hasAttachmentErrors}
submitType="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.

P1: Pressing Cmd/Ctrl+Enter repeatedly while a reply is sending can create duplicate replies and agent tasks. The native form path bypasses the old busy guard, so the shortcut should be prevented while isSubmitting or the action should have an equivalent submission guard.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/Composer.tsx, line 193:

<comment>Pressing Cmd/Ctrl+Enter repeatedly while a reply is sending can create duplicate replies and agent tasks. The native form path bypasses the old `busy` guard, so the shortcut should be prevented while `isSubmitting` or the action should have an equivalent submission guard.</comment>

<file context>
@@ -187,27 +173,31 @@ export function Composer({
-          onSubmit={() => void submit()}
+          submitting={isSubmitting}
+          disabled={isSubmitting || !body.trim() || hasUploading || hasAttachmentErrors}
+          submitType="submit"
         />
-        {error ? <p role="alert" className="ui-error mt-2">{error}</p> : null}
</file context>

body={body}
setBody={setBody}
autoFocus={autoFocus}
focusBodyOnMount={focusBodyOnMount}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Opening a nested reply composer does not focus its textarea because focusBodyOnMount is enabled without supplying the ref that ComposerShell uses for focusing. Wiring a useRef<HTMLTextAreaElement> through this local Composer as textareaRef would restore the intended reply flow.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/flashExperiments/ExperimentDiscussion.tsx, line 264:

<comment>Opening a nested reply composer does not focus its textarea because `focusBodyOnMount` is enabled without supplying the ref that `ComposerShell` uses for focusing. Wiring a `useRef<HTMLTextAreaElement>` through this local `Composer` as `textareaRef` would restore the intended reply flow.</comment>

<file context>
@@ -276,7 +261,7 @@ function Composer({
         body={body}
         setBody={setBody}
-        autoFocus={autoFocus}
+        focusBodyOnMount={focusBodyOnMount}
         placeholder={placeholder}
         rows={2}
</file context>

className="ui-media-outline max-h-80 max-w-full rounded-md bg-black"
>
<source src={attachment.url} type={attachment.contentType} />
<track kind="captions" src="data:text/vtt,WEBVTT" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The new <track kind="captions" src="data:text/vtt,WEBVTT" /> advertises captions that don't exist. It points at an empty WebVTT file (zero cues), so users who enable the CC control get nothing, and since it isn't marked default it's never even selected at runtime. If the goal was to genuinely support captions, ship a real caption source (with srcLang/label); if captions aren't available, drop the track rather than claiming caption availability that the video doesn't deliver — an empty captions track is an accessibility anti-pattern that misrepresents the video's accessibility.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/AttachmentGallery.tsx, line 40:

<comment>The new `<track kind="captions" src="data:text/vtt,WEBVTT" />` advertises captions that don't exist. It points at an empty WebVTT file (zero cues), so users who enable the CC control get nothing, and since it isn't marked `default` it's never even selected at runtime. If the goal was to genuinely support captions, ship a real caption source (with `srcLang`/`label`); if captions aren't available, drop the track rather than claiming caption availability that the video doesn't deliver — an empty captions track is an accessibility anti-pattern that misrepresents the video's accessibility.</comment>

<file context>
@@ -47,6 +37,7 @@ export function AttachmentMedia({
         className="ui-media-outline max-h-80 max-w-full rounded-md bg-black"
       >
         <source src={attachment.url} type={attachment.contentType} />
+        <track kind="captions" src="data:text/vtt,WEBVTT" />
         <a href={attachment.url}>open {attachment.filename}</a>
       </video>
</file context>

titleError={titleTouched && titleMissing ? "add a title." : undefined}
titlePlaceholder={titlePlaceholder}
titleAutoFocus={autoFocusTitle}
focusTitleOnMount={autoFocusTitle}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Wall-post dialog now opens with focus on its close button instead of the title field because this no longer renders the autofocus attribute Dialog uses for fallback focus. Preserve an autofocus marker or provide the wall dialog's title ref to Dialog.initialFocusRef.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/PostForm.tsx, line 246:

<comment>Wall-post dialog now opens with focus on its close button instead of the title field because this no longer renders the `autofocus` attribute Dialog uses for fallback focus. Preserve an autofocus marker or provide the wall dialog's title ref to `Dialog.initialFocusRef`.</comment>

<file context>
@@ -263,7 +243,7 @@ export function PostForm({
         titleError={titleTouched && titleMissing ? "add a title." : undefined}
         titlePlaceholder={titlePlaceholder}
-        titleAutoFocus={autoFocusTitle}
+        focusTitleOnMount={autoFocusTitle}
         titleRequired={requireTitle}
         titleOptional={!requireTitle}
</file context>

event.preventDefault();
onSubmit();
} else {
event.currentTarget.form?.requestSubmit();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The Cmd/Ctrl+Enter fallback can insert a newline into the textarea because this branch requests the form submission without preventing the original key event; the callback branch already suppresses it. Prevent the default event before calling requestSubmit() so the keyboard shortcut submits without editing the draft.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/ComposerShell.tsx, line 119:

<comment>The Cmd/Ctrl+Enter fallback can insert a newline into the textarea because this branch requests the form submission without preventing the original key event; the callback branch already suppresses it. Prevent the default event before calling `requestSubmit()` so the keyboard shortcut submits without editing the draft.</comment>

<file context>
@@ -103,14 +108,16 @@ export function ComposerShell({
+        event.preventDefault();
+        onSubmit();
+      } else {
+        event.currentTarget.form?.requestSubmit();
+      }
     }
</file context>
Suggested change
event.currentTarget.form?.requestSubmit();
event.preventDefault();
event.currentTarget.form?.requestSubmit();


<aside aria-label="Agent panels" className="relative hidden min-w-0 xl:block">
{sidebarOpen ? (
<button

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Sidebar resizing no longer exposes current width or bounds to assistive technology: this button only announces a label while ArrowLeft/ArrowRight change the value. Restore a resize/slider semantic with value and range ARIA properties rather than presenting it as an action button.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/routes/redesign/RedesignPostPage.tsx, line 253:

<comment>Sidebar resizing no longer exposes current width or bounds to assistive technology: this button only announces a label while ArrowLeft/ArrowRight change the value. Restore a resize/slider semantic with value and range ARIA properties rather than presenting it as an action button.</comment>

<file context>
@@ -162,225 +162,209 @@ export function RedesignPostPage() {
+
+      <aside aria-label="Agent panels" className="relative hidden min-w-0 xl:block">
+        {sidebarOpen ? (
+          <button
+            type="button"
+            aria-label="Resize agent sidebar"
</file context>

text: string;
storedPreviews?: FunctionReturnType<typeof api.linkPreviews.get>;
}) {
const previews = [

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Every rich-embed render now reparses and rebuilds the body’s previews in addition to the wrapper’s memoized calculation, and the duplicated logic can drift later. A shared buildPreviews(text) helper or passing the computed previews into RichEmbedListView would keep one derivation and avoid the extra scan.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/RichEmbedList.tsx, line 59:

<comment>Every rich-embed render now reparses and rebuilds the body’s previews in addition to the wrapper’s memoized calculation, and the duplicated logic can drift later. A shared `buildPreviews(text)` helper or passing the computed previews into `RichEmbedListView` would keep one derivation and avoid the extra scan.</comment>

<file context>
@@ -35,9 +46,27 @@ export function RichEmbedList({ text }: { text: string }) {
+  text: string;
+  storedPreviews?: FunctionReturnType<typeof api.linkPreviews.get>;
+}) {
+  const previews = [
+    ...new Map(
+      extractUrls(text)
</file context>

Comment thread package.json
"@edge-runtime/vm": "^5.0.0",
"@rolldown/plugin-babel": "^0.2.3",
"@tailwindcss/vite": "^4.3.1",
"@types/babel__core": "^7.20.5",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The new @types/babel__core (^7.20.5) is bumped a major behind the added @babel/core (^8.0.1); Babel 8 has no matching ⁄types release, and nothing in the source imports either package (only @rolldown/plugin-babel, which brings its own types). Consider dropping the @types/babel__core devDependency so the types don't mislead against the v8 runtime.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At package.json, line 53:

<comment>The new @types/babel__core (^7.20.5) is bumped a major behind the added @babel/core (^8.0.1); Babel 8 has no matching ⁄types release, and nothing in the source imports either package (only @rolldown/plugin-babel, which brings its own types). Consider dropping the @types/babel__core devDependency so the types don't mislead against the v8 runtime.</comment>

<file context>
@@ -36,25 +40,31 @@
     "@edge-runtime/vm": "^5.0.0",
+    "@rolldown/plugin-babel": "^0.2.3",
     "@tailwindcss/vite": "^4.3.1",
+    "@types/babel__core": "^7.20.5",
     "@types/bun": "^1.3.14",
     "@types/node": "^26.0.0",
</file context>

Comment thread convex/orgs.test.ts
expect(orgSlugError("acme-2")).toBeNull();
for (const slug of ["ab", "-acme", "acme-", "Acme", "acme_team", "postwork", "staging"]) {
expect(orgSlugError(slug), slug).not.toBeNull();
expect(orgSlugError(slug)).not.toBeNull();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The removed second argument to expect(...) was a per-iteration diagnostic message. Now if any slug in the loop fails (ab, -acme, acme-, Acme, acme_team, postwork, staging), the assertion failure no longer names which slug caused it, so debugging a regression requires re-running with added logging. Consider keeping a custom message (e.g. expect(orgSlugError(slug), slug).not.toBeNull()) or otherwise including the slug in the assertion output.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At convex/orgs.test.ts, line 30:

<comment>The removed second argument to `expect(...)` was a per-iteration diagnostic message. Now if any slug in the loop fails (`ab`, `-acme`, `acme-`, `Acme`, `acme_team`, `postwork`, `staging`), the assertion failure no longer names which slug caused it, so debugging a regression requires re-running with added logging. Consider keeping a custom message (e.g. `expect(orgSlugError(slug), slug).not.toBeNull()`) or otherwise including the slug in the assertion output.</comment>

<file context>
@@ -27,7 +27,7 @@ describe("organization slugs", () => {
     expect(orgSlugError("acme-2")).toBeNull();
     for (const slug of ["ab", "-acme", "acme-", "Acme", "acme_team", "postwork", "staging"]) {
-      expect(orgSlugError(slug), slug).not.toBeNull();
+      expect(orgSlugError(slug)).not.toBeNull();
     }
   });
</file context>

}) {
return (
<div role="status" aria-label={label} className="w-full">
<output aria-label={label} className="block w-full">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: <output> cannot contain a block-level <div> (its content model is phrasing content), so nesting the skeleton <div aria-hidden> grid inside it is invalid HTML. Since the original intent was an announcer (role="status"), either keep role="status" on a block element or split the layout: put the text in the <output> and the grid in a separate flow element outside it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/Skeleton.tsx, line 13:

<comment>`<output>` cannot contain a block-level `<div>` (its content model is phrasing content), so nesting the skeleton `<div aria-hidden>` grid inside it is invalid HTML. Since the original intent was an announcer (role="status"), either keep `role="status"` on a block element or split the layout: put the text in the `<output>` and the grid in a separate flow element outside it.</comment>

<file context>
@@ -15,14 +10,14 @@ export function Skeleton({
 }) {
   return (
-    <div role="status" aria-label={label} className="w-full">
+    <output aria-label={label} className="block w-full">
       <span className="sr-only">{label}</span>
       <div aria-hidden="true" className="grid gap-3">
</file context>

@cubic-dev-ai cubic-dev-ai 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.

5 issues found across 16 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/routes/SpacesPage.tsx">

<violation number="1" location="src/routes/SpacesPage.tsx:124">
P1: Closing the dialog while creation is pending still creates the space and then redirects to it, even though the user cancelled the flow. Keep the dialog/cancel controls non-dismissible while the action is pending (use `useActionState`'s third pending value).</violation>
</file>

<file name="src/components/ProfileSettingsForm.tsx">

<violation number="1" location="src/components/ProfileSettingsForm.tsx:100">
P2: A failed image upload remains displayed after a later save because the action never clears local `error`, and it masks `submitState.error`; clear the upload error when save submission starts.</violation>
</file>

<file name="src/components/Button.tsx">

<violation number="1" location="src/components/Button.tsx:74">
P2: Passing `loading` to `FormSubmitButton` is ignored whenever its form is idle, so the button is neither rendered as loading nor disabled for caller-controlled async work; preserve the explicit `ButtonProps.loading` value (or omit that prop from this wrapper's public type).</violation>
</file>

<file name="src/components/PostForm.tsx">

<violation number="1" location="src/components/PostForm.tsx:99">
P3: Removing the useMemo here makes `readDraft` run on every render, parsing JSON from sessionStorage on each keystroke. Unlike the pure `SPACES`/`spaces` computations (which the React Compiler re-memoizes automatically), `readDraft` reads impure external storage, so the compiler won't optimize it away. Since the result only seeds useState initializers, consider keeping it memoized with `useMemo(() => readDraft(draftKey), [draftKey])` to avoid the per-render sessionStorage read.</violation>

<violation number="2" location="src/components/PostForm.tsx:137">
P3: The space-options derivation is now duplicated: the render computes `spaceOptions` (line 129) and the useEffect rebuilds the identical array as `options` (line 137) to keep the dependency array on primitives instead of the recreated `spaceOptions`. This is done so the effect doesn't depend on a fresh-array identity each render, but the two copies can silently drift if one is edited and the other isn't, which could make the effect select a space key inconsistent with what the dropdown shows. Consider extracting a single helper that both the render and the effect call (e.g. a module-level `buildSpaceOptions(fixedSpace, spaces)`), so the two are guaranteed to stay in sync.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/routes/SpacesPage.tsx
onClose={onClose}
initialFocusRef={nameRef}
dismissible={!saving}
dismissible

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Closing the dialog while creation is pending still creates the space and then redirects to it, even though the user cancelled the flow. Keep the dialog/cancel controls non-dismissible while the action is pending (use useActionState's third pending value).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/routes/SpacesPage.tsx, line 124:

<comment>Closing the dialog while creation is pending still creates the space and then redirects to it, even though the user cancelled the flow. Keep the dialog/cancel controls non-dismissible while the action is pending (use `useActionState`'s third pending value).</comment>

<file context>
@@ -89,84 +89,75 @@ function CreateSpaceDialog({ onClose }: { onClose: () => void }) {
       onClose={onClose}
       initialFocusRef={nameRef}
-      dismissible={!saving}
+      dismissible
     >
-      <form className="grid gap-4" onSubmit={submit}>
</file context>

}
};
const [submitState, submitAction] = useActionState(
async (_previous: { error: string | null; saved: boolean }, formData: FormData) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: A failed image upload remains displayed after a later save because the action never clears local error, and it masks submitState.error; clear the upload error when save submission starts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/ProfileSettingsForm.tsx, line 100:

<comment>A failed image upload remains displayed after a later save because the action never clears local `error`, and it masks `submitState.error`; clear the upload error when save submission starts.</comment>

<file context>
@@ -100,41 +96,41 @@ export function ProfileSettingsForm() {
-    }
-  };
+  const [submitState, submitAction] = useActionState(
+    async (_previous: { error: string | null; saved: boolean }, formData: FormData) => {
+      const nextName = String(formData.get("name") ?? "").trim();
+      const nextTitle = String(formData.get("title") ?? "").trim();
</file context>
Suggested change
async (_previous: { error: string | null; saved: boolean }, formData: FormData) => {
async (_previous: { error: string | null; saved: boolean }, formData: FormData) => {
setError(null);

Comment thread src/components/Button.tsx
export function FormSubmitButton({ loadingLabel = "working…", children, ...props }: ButtonProps) {
const { pending } = useFormStatus();
return (
<Button {...props} type="submit" loading={pending} loadingLabel={loadingLabel}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Passing loading to FormSubmitButton is ignored whenever its form is idle, so the button is neither rendered as loading nor disabled for caller-controlled async work; preserve the explicit ButtonProps.loading value (or omit that prop from this wrapper's public type).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/Button.tsx, line 74:

<comment>Passing `loading` to `FormSubmitButton` is ignored whenever its form is idle, so the button is neither rendered as loading nor disabled for caller-controlled async work; preserve the explicit `ButtonProps.loading` value (or omit that prop from this wrapper's public type).</comment>

<file context>
@@ -66,3 +67,12 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
+export function FormSubmitButton({ loadingLabel = "working…", children, ...props }: ButtonProps) {
+  const { pending } = useFormStatus();
+  return (
+    <Button {...props} type="submit" loading={pending} loadingLabel={loadingLabel}>
+      {children}
+    </Button>
</file context>
Suggested change
<Button {...props} type="submit" loading={pending} loadingLabel={loadingLabel}>
<Button {...props} type="submit" loading={props.loading || pending} loadingLabel={loadingLabel}>

onSubmit: (fields: PostFormFields) => Promise<void> | void;
}) {
const draft = useMemo(() => readDraft(draftKey), [draftKey]);
const draft = readDraft(draftKey);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Removing the useMemo here makes readDraft run on every render, parsing JSON from sessionStorage on each keystroke. Unlike the pure SPACES/spaces computations (which the React Compiler re-memoizes automatically), readDraft reads impure external storage, so the compiler won't optimize it away. Since the result only seeds useState initializers, consider keeping it memoized with useMemo(() => readDraft(draftKey), [draftKey]) to avoid the per-render sessionStorage read.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/PostForm.tsx, line 99:

<comment>Removing the useMemo here makes `readDraft` run on every render, parsing JSON from sessionStorage on each keystroke. Unlike the pure `SPACES`/`spaces` computations (which the React Compiler re-memoizes automatically), `readDraft` reads impure external storage, so the compiler won't optimize it away. Since the result only seeds useState initializers, consider keeping it memoized with `useMemo(() => readDraft(draftKey), [draftKey])` to avoid the per-render sessionStorage read.</comment>

<file context>
@@ -96,7 +96,7 @@ export function PostForm({
   onSubmit: (fields: PostFormFields) => Promise<void> | void;
 }) {
-  const draft = useMemo(() => readDraft(draftKey), [draftKey]);
+  const draft = readDraft(draftKey);
   const spaces = useSpacesList();
   const [title, setTitle] = useState(draft?.title ?? "");
</file context>

);
if (!hasMatch && spaceOptions[0]) {
setSpaceKey(spaceOptions[0].id ?? spaceOptions[0].label);
const options: SpaceOption[] = fixedSpace

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The space-options derivation is now duplicated: the render computes spaceOptions (line 129) and the useEffect rebuilds the identical array as options (line 137) to keep the dependency array on primitives instead of the recreated spaceOptions. This is done so the effect doesn't depend on a fresh-array identity each render, but the two copies can silently drift if one is edited and the other isn't, which could make the effect select a space key inconsistent with what the dropdown shows. Consider extracting a single helper that both the render and the effect call (e.g. a module-level buildSpaceOptions(fixedSpace, spaces)), so the two are guaranteed to stay in sync.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/PostForm.tsx, line 137:

<comment>The space-options derivation is now duplicated: the render computes `spaceOptions` (line 129) and the useEffect rebuilds the identical array as `options` (line 137) to keep the dependency array on primitives instead of the recreated `spaceOptions`. This is done so the effect doesn't depend on a fresh-array identity each render, but the two copies can silently drift if one is edited and the other isn't, which could make the effect select a space key inconsistent with what the dropdown shows. Consider extracting a single helper that both the render and the effect call (e.g. a module-level `buildSpaceOptions(fixedSpace, spaces)`), so the two are guaranteed to stay in sync.</comment>

<file context>
@@ -125,24 +125,25 @@ export function PostForm({
-    const hasMatch = spaceOptions.some((option) => (option.id ?? option.label) === spaceKey);
-    if (!hasMatch && spaceOptions[0]) {
-      setSpaceKey(spaceOptions[0].id ?? spaceOptions[0].label);
+    const options: SpaceOption[] = fixedSpace
+      ? [fixedSpace]
+      : spaces.length > 0
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant