Skip to content

feat: add project history - #49

Draft
intellild wants to merge 5 commits into
rstackjs:mainfrom
intellild:codex/project-history
Draft

feat: add project history#49
intellild wants to merge 5 commits into
rstackjs:mainfrom
intellild:codex/project-history

Conversation

@intellild

Copy link
Copy Markdown
Contributor

No description provided.

@intellild
intellild marked this pull request as ready for review July 28, 2026 05:43

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98f8f51799

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/store/bundler.ts Outdated
Comment thread src/components/HistoryDrawer.tsx
Comment thread src/store/bundler.ts Outdated
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@intellild, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 40ee2059-ac30-41ae-a4bc-cc025901bc74

📥 Commits

Reviewing files that changed from the base of the PR and between 98f8f51 and e88d6f7.

📒 Files selected for processing (4)
  • src/components/HistoryDrawer.tsx
  • src/components/ui/drawer.tsx
  • src/lib/history.ts
  • src/store/bundler.ts
📝 Walkthrough

Walkthrough

Adds IndexedDB-backed project history with zipped source archives, snapshot listing, restore, duplication, deletion, and renaming. Successful bundles now save history snapshots and track the active project. Editor updates route through a shared bundling helper. The header includes a history button that opens a drawer with snapshot management, new-project actions, loading/error states, and delete confirmation. New drawer, popover, and input UI primitives support the interface.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No PR description was provided, so there is nothing meaningful to evaluate. Add a short description of the main changes, such as project history persistence and the new history drawer.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding project history support.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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: 2

🧹 Nitpick comments (6)
src/lib/history.ts (2)

66-76: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

createArchive doesn't clean up the writer on error.

Unlike restoreHistory's try/finally around zipReader.close() (Line 210-212), if zipWriter.add() throws partway through the loop, zipWriter.close() is never called. Given zip.js's documented usage always pairs add/close, consider wrapping this in try/finally (or try/catch + rethrow) for consistency and to avoid leaving the writer in an inconsistent state.

♻️ Proposed fix
 async function createArchive(files: SourceFile[]) {
   const blobWriter = new BlobWriter("application/zip");
   const zipWriter = new ZipWriter(blobWriter);

-  for (const file of files) {
-    await zipWriter.add(file.filename, new TextReader(file.text));
-  }
-
-  await zipWriter.close();
-  return blobWriter.getData();
+  try {
+    for (const file of files) {
+      await zipWriter.add(file.filename, new TextReader(file.text));
+    }
+    await zipWriter.close();
+    return blobWriter.getData();
+  } catch (error) {
+    await zipWriter.close().catch(() => {});
+    throw error;
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/history.ts` around lines 66 - 76, Update createArchive so
zipWriter.close() is always invoked when adding an archive entry fails,
including errors from zipWriter.add() inside the files loop. Wrap the
add-and-close flow in try/finally, preserve error propagation, and only return
blobWriter.getData() after successful closure.

100-186: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider adding tests for the concurrent-save / dedup logic.

saveHistory's pendingProjectCreation coalescing and the update-vs-create fallback (Lines 128-149) are the most concurrency-sensitive part of this new persistence layer, and interact subtly with bundler.ts's request-staleness guards (see the comment there about a delete race). This logic would benefit from unit tests (e.g., fake-indexeddb + concurrent saveHistory calls) to lock in the intended behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/history.ts` around lines 100 - 186, Add unit tests for saveHistory
covering concurrent calls with a null projectId to verify pendingProjectCreation
coalesces creation and both callers receive the same snapshot, plus the
existing-record update path and update-vs-create fallback when
database.history.update reports no rows. Use the project’s IndexedDB test setup
or fake-indexeddb, and include the relevant cleanup/reset of pending state
between tests.
src/store/bundler.ts (1)

93-136: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

"Bundling…" indicator now also covers history archiving/persistence.

isBundlingAtom/bindingLoadingAtom aren't cleared until the outer finally (Lines 131-136), which now runs after the new archive-zip + saveHistory work (Lines 105-119) completes. For larger projects this extends how long the header shows "Bundling…" past when the actual bundle output is ready, since persistence is unrelated to the user-visible result. Consider clearing the bundling/loading flags right after set(bundleResultAtom, result) and letting history persistence happen in the background.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/store/bundler.ts` around lines 93 - 136, Update the success path in the
bundling flow after set(bundleResultAtom, result) to clear bindingLoadingAtom
and isBundlingAtom immediately once the bundle result is available, guarding
with isLatestRequest(). Keep saveHistory persistence running afterward without
keeping the user-visible bundling indicators active, and prevent the outer
finally from redundantly controlling those flags for this completed request.
src/components/ui/drawer.tsx (2)

49-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate class token.

data-[state=closed]:animate-out appears twice in the same string. Harmless but redundant.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/drawer.tsx` at line 49, Remove the duplicate
data-[state=closed]:animate-out token from the Drawer component’s class string,
preserving all other classes and state-based styling unchanged.

46-59: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Direction-agnostic slide animation hardcodes "right".

DrawerContent's base classes always apply data-[state=closed]:slide-out-to-right / data-[state=open]:slide-in-from-right, but the direction-specific position classes below correctly branch on data-[vaul-drawer-direction=...]. Since vaul's direction prop supports top/bottom/left/right (default bottom), any consumer of this shared primitive that opens from a direction other than right will get content sliding in/out from the wrong side, similar to a known issue in the shadcn-svelte drawer where hardcoded directional classes broke non-bottom drawers. Currently only direction="right" is used (HistoryDrawer.tsx), so this is latent, but it will break the moment another direction is added.

♻️ Make the slide animation direction-aware
         className={cn(
-          "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right fixed z-50 flex h-auto flex-col border shadow-lg duration-200",
+          "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed z-50 flex h-auto flex-col border shadow-lg duration-200",
+          "data-[vaul-drawer-direction=right]:data-[state=closed]:slide-out-to-right data-[vaul-drawer-direction=right]:data-[state=open]:slide-in-from-right",
+          "data-[vaul-drawer-direction=left]:data-[state=closed]:slide-out-to-left data-[vaul-drawer-direction=left]:data-[state=open]:slide-in-from-left",
+          "data-[vaul-drawer-direction=top]:data-[state=closed]:slide-out-to-top data-[vaul-drawer-direction=top]:data-[state=open]:slide-in-from-top",
+          "data-[vaul-drawer-direction=bottom]:data-[state=closed]:slide-out-to-bottom data-[vaul-drawer-direction=bottom]:data-[state=open]:slide-in-from-bottom",
           "data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/drawer.tsx` around lines 46 - 59, Update DrawerContent’s
direction-specific animation classes so open and closed slide transitions match
the active data-[vaul-drawer-direction] value for top, bottom, left, and right,
rather than always using the rightward animation. Preserve the existing
direction-specific positioning and fade behavior, and ensure the default bottom
direction slides vertically.
src/components/HistoryDrawer.tsx (1)

226-228: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unnecessary Fragment.

The <>...</> wraps a single <Drawer> child; it can be dropped.

Also applies to: 460-462

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/HistoryDrawer.tsx` around lines 226 - 228, Remove the
unnecessary fragment wrapping the single Drawer element in the HistoryDrawer
render at the shown location, and apply the same simplification to the
corresponding fragment around lines 460-462. Preserve the Drawer structure and
behavior unchanged.
🤖 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 `@src/components/HistoryDrawer.tsx`:
- Around line 393-404: Update the Delete trigger and Restore button disabled
conditions in the HistoryDrawer component to also check isStartingNewProject,
matching the existing Copy and Edit button behavior. Preserve the current busyId
checks while preventing either action during handleStartNewProject.

In `@src/store/bundler.ts`:
- Around line 105-119: Before calling saveHistory in the bundle save flow,
re-read currentProjectIdAtom and skip the save when its live value no longer
matches the request’s expected project ID, including when the captured projectId
was non-null and the project was deleted. Update the logic around
projectIdForSave and saveHistory so only a still-valid project request is
persisted; preserve the existing snapshot activation and error handling for
valid saves.

---

Nitpick comments:
In `@src/components/HistoryDrawer.tsx`:
- Around line 226-228: Remove the unnecessary fragment wrapping the single
Drawer element in the HistoryDrawer render at the shown location, and apply the
same simplification to the corresponding fragment around lines 460-462. Preserve
the Drawer structure and behavior unchanged.

In `@src/components/ui/drawer.tsx`:
- Line 49: Remove the duplicate data-[state=closed]:animate-out token from the
Drawer component’s class string, preserving all other classes and state-based
styling unchanged.
- Around line 46-59: Update DrawerContent’s direction-specific animation classes
so open and closed slide transitions match the active
data-[vaul-drawer-direction] value for top, bottom, left, and right, rather than
always using the rightward animation. Preserve the existing direction-specific
positioning and fade behavior, and ensure the default bottom direction slides
vertically.

In `@src/lib/history.ts`:
- Around line 66-76: Update createArchive so zipWriter.close() is always invoked
when adding an archive entry fails, including errors from zipWriter.add() inside
the files loop. Wrap the add-and-close flow in try/finally, preserve error
propagation, and only return blobWriter.getData() after successful closure.
- Around line 100-186: Add unit tests for saveHistory covering concurrent calls
with a null projectId to verify pendingProjectCreation coalesces creation and
both callers receive the same snapshot, plus the existing-record update path and
update-vs-create fallback when database.history.update reports no rows. Use the
project’s IndexedDB test setup or fake-indexeddb, and include the relevant
cleanup/reset of pending state between tests.

In `@src/store/bundler.ts`:
- Around line 93-136: Update the success path in the bundling flow after
set(bundleResultAtom, result) to clear bindingLoadingAtom and isBundlingAtom
immediately once the bundle result is available, guarding with
isLatestRequest(). Keep saveHistory persistence running afterward without
keeping the user-visible bundling indicators active, and prevent the outer
finally from redundantly controlling those flags for this completed request.
🪄 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: 172c7894-b8e6-4f17-a7f1-017798a52226

📥 Commits

Reviewing files that changed from the base of the PR and between 7e33bee and 98f8f51.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • package.json
  • src/components/Editor/index.tsx
  • src/components/Header.tsx
  • src/components/HistoryDrawer.tsx
  • src/components/ui/drawer.tsx
  • src/components/ui/input.tsx
  • src/components/ui/popover.tsx
  • src/lib/history.ts
  • src/store/bundler.ts

Comment thread src/components/HistoryDrawer.tsx Outdated
Comment thread src/store/bundler.ts
@intellild
intellild marked this pull request as draft July 28, 2026 11:11
@intellild

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e88d6f76a8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/store/bundler.ts Outdated
}
}

const currentProjectIdStateAtom = atom<number | null>(getStoredCurrentProjectId());

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 Badge Do not reuse a project ID without restoring its files

After a project has been saved, reloading the page or opening another same-origin tab initializes this atom from localStorage, while inputFilesAtom is initialized independently from the URL hash or the default preset. The editor's automatic initial bundle then passes the persisted ID to saveHistory, overwriting that project's only archive with unrelated or stale files before the user can restore it. Either restore the stored project's files/version before the initial bundle or keep the active ID session-local/clear it at startup.

Useful? React with 👍 / 👎.

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