Skip to content

feat: Phase 4-5 — Templates, Deploy, Model Setup, README#6

Merged
Szymon0C merged 18 commits intomainfrom
claude/gifted-satoshi
Apr 3, 2026
Merged

feat: Phase 4-5 — Templates, Deploy, Model Setup, README#6
Szymon0C merged 18 commits intomainfrom
claude/gifted-satoshi

Conversation

@Szymon0C
Copy link
Copy Markdown
Owner

@Szymon0C Szymon0C commented Apr 2, 2026

Summary

  • Template Gallery — Curated template browser with category filters, scaled iframe previews, and AI customization flow (?prompt= query param)
  • Deploy System — One-click deploy to Netlify, Vercel, or GitHub Pages via Rust backend; token management in SQLite; DeployModal with 3-step flow (provider → token → progress/success); local HTML export via dialog plugin
  • Model Download & Setup — In-app AI model selection, download with progress tracking via Tauri events, auto-start sidecar on existing model detection
  • Sidecar Improvements — Health check fallback to /v1/models, llama.cpp upgraded to b7472
  • TopBar — Functional viewport toggle (Desktop/Tablet/Mobile) and Deploy button
  • README Redesign — Modern badges, Getting Started instructions, architecture diagram, project structure

New files

  • src-tauri/src/deploy.rs — Rust deploy commands (Netlify, Vercel, GitHub Pages, export)
  • src-tauri/src/models.rs — Model download, existence check, path resolution
  • src/components/deploy/DeployModal.tsx — Deploy modal with provider selection
  • src/components/chat/ModelSetup.tsx — First-time AI model setup UI
  • src/components/templates/TemplateCard.tsx — Template card with iframe preview
  • src/components/templates/TemplatePreviewModal.tsx — Template preview + AI customization
  • src/stores/deployStore.ts — Deploy state + token persistence
  • src/stores/templateStore.ts — Template state + filtering
  • src/lib/deployProviders.ts — Provider metadata
  • src/lib/bundledTemplates.ts — 4 bundled HTML templates

Test plan

  • TypeScript compiles clean (npx tsc --noEmit)
  • Vite build succeeds (npm run build)
  • Template gallery loads with category filters
  • Template preview modal shows full preview
  • "Customize with AI" flow navigates to project with prompt
  • Deploy modal opens from TopBar button
  • Token input and provider selection work
  • Viewport toggle switches preview sizes
  • Model setup shows on first launch when no model exists

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Template gallery with four bundled templates, category filters, previews, and AI-assisted customization; “From Template” quick-start on Home
    • One-click deploy & export UI (Netlify, Vercel, GitHub Pages, local HTML) with token entry and deploy status
    • Local model management: list, download, progress UI, and automatic sidecar start
  • Documentation

    • Updated README and added deployment roadmap/plan
  • Chores

    • Added dialog/plugin support for native save dialogs

Szymon0C and others added 7 commits April 3, 2026 00:01
9 tasks: bundled templates, template store, DB seeding, template card,
preview modal, gallery page, AI customization flow, HomePage button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- TemplatesPage: full gallery with category filter, template grid,
  preview modal, and project creation from template
- ProjectPage: auto-send ?prompt= query param for template AI customization
- HomePage: add "From Template" button alongside "New Project"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 2, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 627beee7-5bc9-4798-90b6-0f436355b4ed

📥 Commits

Reviewing files that changed from the base of the PR and between ad56d22 and 5ec2d3a.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • README.md
  • docs/superpowers/plans/2026-04-03-phase5-deploy.md
  • package.json
  • scripts/setup-sidecar.sh
  • src-tauri/Cargo.toml
  • src-tauri/capabilities/default.json
  • src-tauri/src/deploy.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/models.rs
  • src-tauri/src/sidecar.rs
  • src/components/chat/ChatPanel.tsx
  • src/components/chat/ModelSetup.tsx
  • src/components/deploy/DeployModal.tsx
  • src/components/layout/AppShell.tsx
  • src/components/layout/TopBar.tsx
  • src/components/preview/PreviewFrame.tsx
  • src/lib/deployProviders.ts
  • src/pages/ProjectPage.tsx
  • src/stores/aiStore.ts
  • src/stores/deployStore.ts

Walkthrough

Adds a template gallery and seeding (bundled HTML templates + DB seed), a Zustand template store, UI components (cards + preview modal), project creation with optional AI customization, and broader platform additions for deployment, model management, sidecar changes, and related UI wiring.

Changes

Cohort / File(s) Summary
Templates: data, store, UI, pages
src/lib/bundledTemplates.ts, src/stores/templateStore.ts, src/components/templates/TemplateCard.tsx, src/components/templates/TemplatePreviewModal.tsx, src/pages/TemplatesPage.tsx, src/pages/HomePage.tsx, src/pages/ProjectPage.tsx, src/db/database.ts
Adds 4 bundled HTML templates and DB seeding; new Zustand template store with load/filter; TemplateCard and TemplatePreviewModal UI; TemplatesPage gallery with category filters and create/use flows; HomePage "From Template" button; ProjectPage reads ?prompt= to auto-run AI edit once.
AI/model & sidecar flow
src/components/chat/ChatPanel.tsx, src/components/chat/ModelSetup.tsx, src/stores/aiStore.ts, scripts/setup-sidecar.sh, src-tauri/src/sidecar.rs
Auto-initialize/detect hardware, model listing/download and start sidecar flows; ModelSetup UI and download progress; store actions to list/download/check models; sidecar readiness probe & shutdown behavior updated; updated local install script Llama version and extraction logic.
Deployment: backend commands & frontend modal/store
src-tauri/src/deploy.rs, src-tauri/src/models.rs, src-tauri/src/lib.rs, src-tauri/Cargo.toml, src-tauri/capabilities/default.json, src/lib/deployProviders.ts, src/stores/deployStore.ts, src/components/deploy/DeployModal.tsx, src/components/layout/AppShell.tsx, src/components/layout/TopBar.tsx
Adds Tauri commands to deploy/export HTML to Netlify/Vercel/GitHub Pages and to manage/download models; registers new commands and tauri dialog plugin; adds deploy provider metadata, a deploy Zustand store, DeployModal UI, TopBar deploy button wiring, and AppShell inclusion.
Preview/editor UX adjustments
src/components/preview/PreviewFrame.tsx, src/pages/ProjectPage.tsx, src/components/layout/TopBar.tsx
Tighter HTML existence checks for preview iframe, layout tweaks (min-h-0), viewport controls in TopBar, and AI generation streaming usage extended for project prompt flow.
Chat UI & input flow
src/components/chat/ChatPanel.tsx, src/components/chat/ModelSetup.tsx
Reworked chat panel to surface model download/progress and start AI automatically; conditional ModelSetup rendering and updated placeholders/disabled states for input.
Store & plumbing changes
src/stores/* (aiStore.ts, templateStore.ts, deployStore.ts), src/db/database.ts
New/expanded Zustand stores (templates, deploy, ai model management) and DB seeding for templates; token persistence for deploy tokens via settings table.
Docs, scripts, packaging
README.md, docs/superpowers/plans/2026-04-02-phase4-templates.md, docs/superpowers/plans/2026-04-03-phase5-deploy.md, package.json, scripts/setup-sidecar.sh
Extensive README rewrite; added Phase 5 deploy plan; package.json adds tauri dialog plugin; setup-sidecar script Llama version and extraction behavior updated.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Browser as Browser/UI
    participant TemplatesPage as TemplatesPage
    participant TemplateStore as Template Store
    participant DB as Database
    participant Modal as Preview Modal
    participant Backend as Tauri/API
    participant ProjectPage as ProjectPage
    participant AI as AI sidecar

    User->>Browser: Click "From Template"
    Browser->>TemplatesPage: navigate /templates
    TemplatesPage->>TemplateStore: loadTemplates()
    TemplateStore->>DB: SELECT templates
    DB-->>TemplateStore: templates list
    TemplateStore-->>TemplatesPage: templates loaded
    User->>TemplatesPage: click TemplateCard
    TemplatesPage->>Modal: open preview
    User->>Modal: "Customize with AI" + submit prompt
    Modal->>TemplatesPage: onUse(templateId, prompt)
    TemplatesPage->>DB: createProject()
    DB-->>TemplatesPage: projectId
    TemplatesPage->>DB: updateProjectHtml(projectId, templateHtml)
    TemplatesPage->>Browser: navigate /project/:id?prompt=...
    ProjectPage->>ProjectPage: detect ?prompt
    ProjectPage->>Backend: invoke generate(projectId, prompt)
    Backend->>AI: stream / process
    AI-->>Backend: generated HTML
    Backend-->>ProjectPage: result
    ProjectPage->>DB: persist snapshot / update project
    ProjectPage-->>User: show customized project
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰 In a burrow of HTML, templates gleam bright,
Cards and modals hop into sight,
Pick, preview, tweak with a whisper of AI,
Projects sprout wings and learn how to fly—
A rabbit cheers, "Templates take flight!" 🥕✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

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.
Title check ⚠️ Warning The PR title mentions Phase 4-5 and lists multiple features (Templates, Deploy, Model Setup, README), but the actual changeset is narrowly focused on Phase 4 Templates only, making the title overly broad and partially misleading. Revise the title to reflect the actual changes: 'feat: Phase 4 — Templates Gallery' or similar, removing references to Deploy, Model Setup, and README which are not implemented in this PR.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/gifted-satoshi

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (6)
src/db/database.ts (1)

36-45: Seed by template id, not by table emptiness.

Once any row exists here, later bundled additions or HTML/version refreshes never land, and a partial first-run seed leaves the gallery permanently incomplete. Prefer per-template INSERT ... ON CONFLICT/INSERT OR IGNORE inside a transaction instead of the global COUNT(*) gate.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/db/database.ts` around lines 36 - 45, seedTemplates currently skips
seeding when the templates table is non-empty, which prevents adding or updating
bundled templates later; change seedTemplates to open a transaction and for each
template in BUNDLED_TEMPLATES perform a per-template upsert/insert-or-ignore
(e.g., use INSERT ... ON CONFLICT or INSERT OR IGNORE depending on DB adapter)
keyed by tpl.id and then update html/version if needed, rather than relying on
the global COUNT(*) check, ensuring the operation is idempotent and individual
templates are added/updated inside the transaction.
docs/superpowers/plans/2026-04-02-phase4-templates.md (5)

296-308: Consider tracking error state for better UX.

The loadTemplates function logs errors but doesn't expose error state to consumers. Consider adding an error field to enable UI feedback when template loading fails.

♻️ Proposed enhancement
 interface TemplateState {
   templates: Template[];
   filter: SiteType | 'all';
   loading: boolean;
+  error: string | null;
   loadTemplates: () => Promise<void>;
   setFilter: (filter: SiteType | 'all') => void;
   filteredTemplates: () => Template[];
 }

 export const useTemplateStore = create<TemplateState>((set, get) => ({
   templates: [],
   filter: 'all',
   loading: false,
+  error: null,

   loadTemplates: async () => {
-    set({ loading: true });
+    set({ loading: true, error: null });
     try {
       const db = await getDatabase();
       const templates = await db.select<Template[]>(
         'SELECT id, name, category, html, version FROM templates ORDER BY category, name'
       );
       set({ templates, loading: false });
     } catch (error) {
       console.error('Failed to load templates:', error);
-      set({ loading: false });
+      set({ loading: false, error: 'Failed to load templates' });
     }
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-02-phase4-templates.md` around lines 296 -
308, The loadTemplates function currently swallows errors by only logging them
and toggling loading; update the state shape to include an error field and set
it on failure so consumers can react. Add an error property to whatever state is
managed by set (used in loadTemplates), clear error at start of loadTemplates
(set({ loading: true, error: null })) and on success (set({ templates, loading:
false, error: null })), and on catch set the error value (set({ loading: false,
error })) so callers of getDatabase/loadTemplates can show UI feedback.

16-16: Add blank lines around tables for markdown compliance.

The tables in the "File Structure" section are missing surrounding blank lines, violating markdown best practices (MD058). While this doesn't affect functionality, it improves readability and ensures consistent rendering across markdown parsers.

📝 Proposed formatting fix

Add blank lines before and after both tables:

 ### New Files
+
 | File | Responsibility |
 |------|---------------|
 | `src/stores/templateStore.ts` | Zustand store: load templates from DB, filter by category |
 ...
+
 ### Modified Files
+
 | File | Changes |
 |------|---------|
 | `src/pages/TemplatesPage.tsx` | Replace placeholder with template gallery grid + category filter |
 ...
+
 ---

Also applies to: 24-24

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-02-phase4-templates.md` at line 16, The "File
Structure" section's markdown tables (e.g., the table starting with the header
string "| File | Responsibility |") lack blank lines before and after them;
update the document to insert a single empty line immediately above and below
each table in that section (including the other table referenced at the same
location) so the tables are surrounded by blank lines and comply with MD058.

9-9: Consider leveraging React 19 features for form handling.

While the implementation is compatible with React 19, it doesn't utilize new features like Actions, useActionState, or useOptimistic that could enhance the template customization flow. As per library documentation, React 19 introduces async Actions that automatically track pending/error state—this could simplify the loading states in the template store and modal.

Potential enhancements:

  • Use useActionState for template loading in the store
  • Use useOptimistic for instant project creation feedback before DB confirm
  • Use form Actions for the template customization prompt input

This is optional—the current implementation works correctly, but React 19 features could reduce boilerplate.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-02-phase4-templates.md` at line 9, Update the
template loading and project-creation flows to use React 19 actions and hooks:
replace manual loading/error state in the template store (e.g., TemplateStore
and its loadTemplates or fetchTemplates methods accessed via useTemplateStore)
with useActionState tied to an async Action (useAction or createAction) so
pending/error are auto-tracked; switch createProject in the project flow to use
useOptimistic to apply an immediate optimistic update to the projects list and
rollback on failure; convert the template customization form handlers in
TemplateModal (or the component handling the prompt input) to use a form Action
(useAction) so the prompt submission is async and its state is observable via
useActionState; ensure these changes keep existing API shapes (method names like
loadTemplates, createProject, TemplateModal) so caller code requires minimal
adjustment.

516-523: Document the security model for iframe sandboxing.

The preview modal uses sandbox="allow-scripts" to enable interactive template previews. This is safe for the current implementation (bundled templates only), but creates a potential XSS risk if the feature is extended to support user-uploaded templates.

Consider adding a code comment to document this security boundary:

📝 Proposed documentation comment
         {/* Preview */}
         <div className="flex-1 bg-white overflow-hidden">
+          {/* 
+            Security: allow-scripts is safe here because templates come from BUNDLED_TEMPLATES.
+            WARNING: If user-uploaded templates are added in the future, this must be changed
+            to sandbox="" or the templates must be sanitized to prevent XSS attacks.
+          */}
           <iframe
             srcDoc={template.html}
             title={template.name}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-02-phase4-templates.md` around lines 516 -
523, Add a concise in-code comment immediately above the iframe in the preview
component (the JSX block that renders srcDoc={template.html} and
sandbox="allow-scripts") that documents the security model: note that
sandbox="allow-scripts" is intentionally used to enable interactive bundled
templates, assert that this is safe because only vetted/bundled templates are
allowed, and warn that enabling user-uploaded templates would introduce XSS
risk; include recommended mitigation steps if the feature is extended (e.g.,
remove allow-scripts, sanitize/validate templates, or render in a more
restricted isolated context).

621-633: Consider URL length limits for customization prompts.

The prompt is passed via query parameter (line 632), which has browser-imposed length limits (typically ~2000 characters). Long AI customization prompts could be truncated or cause navigation failures.

Consider one of these alternatives:

  1. Session storage: Store the prompt in sessionStorage and pass only a flag
  2. Request body: Use React Router's state prop to pass the prompt
  3. Length validation: Add a character limit with user feedback
♻️ Example using React Router state
-    navigate(`/project/${project.id}${customPrompt ? `?prompt=${encodeURIComponent(customPrompt)}` : ''}`);
+    navigate(`/project/${project.id}`, {
+      state: customPrompt ? { initialPrompt: customPrompt } : undefined
+    });

Then in ProjectPage (Task 7), read from useLocation().state instead of searchParams.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-02-phase4-templates.md` around lines 621 -
633, The current handleUseTemplate passes customPrompt via a query string in
navigate (navigate(`/project/${project.id}${customPrompt ? `?prompt=...` :
''}`)), which can exceed browser URL length limits; update handleUseTemplate to
avoid long query params by either (a) storing the prompt under a temporary key
in sessionStorage (e.g., sessionStorage.setItem(`templatePrompt:${project.id}`,
customPrompt) and navigate to `/project/${project.id}`), or (b) using React
Router's navigate(..., { state: { prompt: customPrompt } }) so the ProjectPage
reads useLocation().state.prompt; additionally add a fallback length check in
handleUseTemplate to show user feedback if prompt exceeds a safe threshold
(e.g., ~1900 chars) and reference the functions createProject,
updateProjectHtml, setCurrentProject, and navigate when making these changes so
you update the prompt-passing logic atomically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/superpowers/plans/2026-04-02-phase4-templates.md`:
- Around line 732-751: The auto-prompt effect has race conditions: the ref
initialPromptSent never resets, setSearchParams clears the prompt before async
generate() completes, the .then() runs even after unmount, and streaming is a
weak guard; fix by tracking the processed prompt value (e.g., store
lastProcessedPrompt instead of a boolean ref), include
searchParams.get('prompt'), currentProject?.id, streaming and
lastProcessedPrompt in the useEffect deps, call setSearchParams only after
successful generate() completes, add try/catch around generate() to handle
errors and allow retries (do not clear params on failure), and use an
abort/cleanup flag (or AbortController) so the addMessage and createSnapshot
calls inside the generate result handler only run if the component is still
mounted and the prompt matches lastProcessedPrompt; update references to
initialPromptSent, setSearchParams, generate, addMessage, createSnapshot,
buildEditMessages and searchParams accordingly.

In `@src/components/templates/TemplatePreviewModal.tsx`:
- Around line 29-37: The Escape key only triggers onClose when the prompt input
has focus because handleKeyDown is currently attached to the input; instead add
a global/dialog-level keydown listener (e.g., in TemplatePreviewModal's
root/modal container or via useEffect on mount) that listens for 'Escape' and
calls onClose, and keep the Enter/Shift-Enter behavior scoped to the prompt
input (leave handleUseWithAi tied to the input's onKeyDown or create a separate
input-specific handler). Ensure you register the listener on mount and remove it
on unmount to avoid leaks and do not change the existing onClose or
handleUseWithAi implementations.
- Around line 4-7: The TemplatePreviewModal currently calls the prop onUse
synchronously allowing multiple clicks to queue duplicate imports; add a
single-flight guard in the TemplatePreviewModal component: introduce local state
(e.g., isSubmitting or isImporting) and set it true immediately when the CTA
handlers invoke onUse, await the promise returned by onUse (ensure onUse returns
a Promise), and only reset or navigate after it resolves; also disable both CTA
buttons and ignore further clicks while isSubmitting is true to prevent
duplicate project creation. Reference the TemplatePreviewModal component and the
onUse prop in TemplatePreviewModalProps when adding the guard.

In `@src/pages/ProjectPage.tsx`:
- Around line 32-50: The effect that auto-runs template customization sends
messages and snapshots fire-and-forget and doesn't handle generate() rejections;
update the useEffect block so it awaits the async sequence: call
addMessage(currentProject.id, 'user', prompt, 'chat') if it returns a promise or
ensure it completes, then await generate(currentProject.id,
SYSTEM_PROMPTS.editFull, buildEditMessages(...)) inside a try/catch, and only on
success await createSnapshot(currentProject.id, result, `Template customization:
${prompt.slice(0,50)}`); also reset initialPromptSent.current on errors to allow
retries and clear or handle promise rejections to avoid unhandled rejections
(refer to useEffect, initialPromptSent, addMessage, generate, createSnapshot,
buildEditMessages).

In `@src/pages/TemplatesPage.tsx`:
- Around line 36-42: The current handleUseTemplate flow calls createProject(...)
then updateProjectHtml(...), which can leave a blank project if the second call
fails; change this to an atomic operation by adding a single server-side API
(e.g., createProjectWithHtml) or a transactional backend method that creates the
project and writes tpl.html in one DB transaction, then call that from
handleUseTemplate and only call setCurrentProject after the atomic call returns;
if a server change isn’t possible, implement client-side rollback in
handleUseTemplate: wrap updateProjectHtml(...) in try/catch and on failure call
deleteProject(project.id) to clean up and rethrow/report the error so the UI
doesn’t show a blank project.

---

Nitpick comments:
In `@docs/superpowers/plans/2026-04-02-phase4-templates.md`:
- Around line 296-308: The loadTemplates function currently swallows errors by
only logging them and toggling loading; update the state shape to include an
error field and set it on failure so consumers can react. Add an error property
to whatever state is managed by set (used in loadTemplates), clear error at
start of loadTemplates (set({ loading: true, error: null })) and on success
(set({ templates, loading: false, error: null })), and on catch set the error
value (set({ loading: false, error })) so callers of getDatabase/loadTemplates
can show UI feedback.
- Line 16: The "File Structure" section's markdown tables (e.g., the table
starting with the header string "| File | Responsibility |") lack blank lines
before and after them; update the document to insert a single empty line
immediately above and below each table in that section (including the other
table referenced at the same location) so the tables are surrounded by blank
lines and comply with MD058.
- Line 9: Update the template loading and project-creation flows to use React 19
actions and hooks: replace manual loading/error state in the template store
(e.g., TemplateStore and its loadTemplates or fetchTemplates methods accessed
via useTemplateStore) with useActionState tied to an async Action (useAction or
createAction) so pending/error are auto-tracked; switch createProject in the
project flow to use useOptimistic to apply an immediate optimistic update to the
projects list and rollback on failure; convert the template customization form
handlers in TemplateModal (or the component handling the prompt input) to use a
form Action (useAction) so the prompt submission is async and its state is
observable via useActionState; ensure these changes keep existing API shapes
(method names like loadTemplates, createProject, TemplateModal) so caller code
requires minimal adjustment.
- Around line 516-523: Add a concise in-code comment immediately above the
iframe in the preview component (the JSX block that renders
srcDoc={template.html} and sandbox="allow-scripts") that documents the security
model: note that sandbox="allow-scripts" is intentionally used to enable
interactive bundled templates, assert that this is safe because only
vetted/bundled templates are allowed, and warn that enabling user-uploaded
templates would introduce XSS risk; include recommended mitigation steps if the
feature is extended (e.g., remove allow-scripts, sanitize/validate templates, or
render in a more restricted isolated context).
- Around line 621-633: The current handleUseTemplate passes customPrompt via a
query string in navigate (navigate(`/project/${project.id}${customPrompt ?
`?prompt=...` : ''}`)), which can exceed browser URL length limits; update
handleUseTemplate to avoid long query params by either (a) storing the prompt
under a temporary key in sessionStorage (e.g.,
sessionStorage.setItem(`templatePrompt:${project.id}`, customPrompt) and
navigate to `/project/${project.id}`), or (b) using React Router's navigate(...,
{ state: { prompt: customPrompt } }) so the ProjectPage reads
useLocation().state.prompt; additionally add a fallback length check in
handleUseTemplate to show user feedback if prompt exceeds a safe threshold
(e.g., ~1900 chars) and reference the functions createProject,
updateProjectHtml, setCurrentProject, and navigate when making these changes so
you update the prompt-passing logic atomically.

In `@src/db/database.ts`:
- Around line 36-45: seedTemplates currently skips seeding when the templates
table is non-empty, which prevents adding or updating bundled templates later;
change seedTemplates to open a transaction and for each template in
BUNDLED_TEMPLATES perform a per-template upsert/insert-or-ignore (e.g., use
INSERT ... ON CONFLICT or INSERT OR IGNORE depending on DB adapter) keyed by
tpl.id and then update html/version if needed, rather than relying on the global
COUNT(*) check, ensuring the operation is idempotent and individual templates
are added/updated inside the transaction.
🪄 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

Run ID: 657a0859-9a8d-4eaa-8ce2-d2a02d1daa2b

📥 Commits

Reviewing files that changed from the base of the PR and between 98201cf and ad56d22.

📒 Files selected for processing (9)
  • docs/superpowers/plans/2026-04-02-phase4-templates.md
  • src/components/templates/TemplateCard.tsx
  • src/components/templates/TemplatePreviewModal.tsx
  • src/db/database.ts
  • src/lib/bundledTemplates.ts
  • src/pages/HomePage.tsx
  • src/pages/ProjectPage.tsx
  • src/pages/TemplatesPage.tsx
  • src/stores/templateStore.ts

Comment on lines +732 to +751
useEffect(() => {
const prompt = searchParams.get('prompt');
if (prompt && currentProject?.html && !initialPromptSent.current && !streaming) {
initialPromptSent.current = true;
setSearchParams({}, { replace: true });

// Send the customization prompt as a chat edit
const chatHistory: Array<{ role: string; content: string }> = [];
generate(
currentProject.id,
SYSTEM_PROMPTS.editFull,
buildEditMessages(currentProject.html, chatHistory, prompt)
).then((result) => {
if (result) {
addMessage(currentProject.id, 'user', prompt, 'chat');
createSnapshot(currentProject.id, result, `Template customization: ${prompt.slice(0, 50)}`);
}
});
}
}, [searchParams, currentProject, streaming]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Address potential race conditions in auto-prompt logic.

The current implementation has several timing issues:

  1. Ref never resets: initialPromptSent is never reset, preventing re-execution even for legitimate cases (e.g., navigating to a different project with a prompt)
  2. Early param clearing: setSearchParams (line 736) clears the prompt before generate() completes—if generation fails, the prompt is lost
  3. Missing cleanup: The .then() callback executes even if the component unmounts
  4. Weak streaming check: The !streaming condition (line 734) doesn't prevent race conditions since generate() is async
🔧 Proposed fix with better state management
   const [searchParams, setSearchParams] = useSearchParams();
-  const initialPromptSent = useRef(false);
+  const processedPromptRef = useRef<string | null>(null);

   // Auto-send template customization prompt
   useEffect(() => {
     const prompt = searchParams.get('prompt');
-    if (prompt && currentProject?.html && !initialPromptSent.current && !streaming) {
-      initialPromptSent.current = true;
-      setSearchParams({}, { replace: true });
+    if (
+      prompt &&
+      currentProject?.html &&
+      processedPromptRef.current !== prompt &&
+      !streaming
+    ) {
+      processedPromptRef.current = prompt;

       // Send the customization prompt as a chat edit
       const chatHistory: Array<{ role: string; content: string }> = [];
       generate(
         currentProject.id,
         SYSTEM_PROMPTS.editFull,
         buildEditMessages(currentProject.html, chatHistory, prompt)
       ).then((result) => {
         if (result) {
           addMessage(currentProject.id, 'user', prompt, 'chat');
           createSnapshot(currentProject.id, result, `Template customization: ${prompt.slice(0, 50)}`);
         }
+        // Clear params only after successful generation
+        setSearchParams({}, { replace: true });
-      });
+      }).catch((error) => {
+        console.error('Failed to generate template customization:', error);
+        processedPromptRef.current = null; // Allow retry
+      });
     }
-  }, [searchParams, currentProject, streaming]);
+  }, [searchParams, currentProject?.id, currentProject?.html, streaming, setSearchParams, generate]);

Changes:

  • Track the processed prompt value (not just a boolean) to allow different prompts
  • Clear params only after successful generation
  • Add error handling with retry capability
  • Include all dependencies in the effect's dependency array
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-02-phase4-templates.md` around lines 732 -
751, The auto-prompt effect has race conditions: the ref initialPromptSent never
resets, setSearchParams clears the prompt before async generate() completes, the
.then() runs even after unmount, and streaming is a weak guard; fix by tracking
the processed prompt value (e.g., store lastProcessedPrompt instead of a boolean
ref), include searchParams.get('prompt'), currentProject?.id, streaming and
lastProcessedPrompt in the useEffect deps, call setSearchParams only after
successful generate() completes, add try/catch around generate() to handle
errors and allow retries (do not clear params on failure), and use an
abort/cleanup flag (or AbortController) so the addMessage and createSnapshot
calls inside the generate result handler only run if the component is still
mounted and the prompt matches lastProcessedPrompt; update references to
initialPromptSent, setSearchParams, generate, addMessage, createSnapshot,
buildEditMessages and searchParams accordingly.

Comment on lines +4 to +7
interface TemplatePreviewModalProps {
template: Template;
onUse: (templateId: string, customPrompt?: string) => void;
onClose: () => void;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent duplicate project creation on repeated clicks.

onUse(...) is treated as synchronous here, so both CTAs remain clickable until navigation completes. A double-click can queue multiple template imports before the route changes.

Suggested single-flight guard
 interface TemplatePreviewModalProps {
   template: Template;
-  onUse: (templateId: string, customPrompt?: string) => void;
+  onUse: (templateId: string, customPrompt?: string) => Promise<void>;
   onClose: () => void;
 }

 export function TemplatePreviewModal({ template, onUse, onClose }: TemplatePreviewModalProps) {
   const [showPrompt, setShowPrompt] = useState(false);
   const [prompt, setPrompt] = useState('');
+  const [submitting, setSubmitting] = useState(false);

-  const handleUseDirectly = () => {
-    onUse(template.id);
-  };
+  const handleUseDirectly = async () => {
+    if (submitting) return;
+    setSubmitting(true);
+    try {
+      await onUse(template.id);
+    } finally {
+      setSubmitting(false);
+    }
+  };

-  const handleUseWithAi = () => {
+  const handleUseWithAi = async () => {
+    if (submitting) return;
     if (!showPrompt) {
       setShowPrompt(true);
       return;
     }
     const text = prompt.trim();
     if (text) {
-      onUse(template.id, text);
+      setSubmitting(true);
+      try {
+        await onUse(template.id, text);
+      } finally {
+        setSubmitting(false);
+      }
     }
   };
 ...
             <button
               type="button"
               onClick={handleUseDirectly}
+              disabled={submitting}
               className="bg-[var(--color-bg-elevated)] text-[var(--color-text-primary)] text-sm px-4 py-2 rounded-lg hover:bg-[var(--color-bg-primary)] transition-colors"
             >
...
             <button
               type="button"
               onClick={handleUseWithAi}
-              disabled={showPrompt && !prompt.trim()}
+              disabled={submitting || (showPrompt && !prompt.trim())}
               className="bg-[var(--color-accent)] text-white text-sm px-4 py-2 rounded-lg hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
             >

Also applies to: 14-27, 84-98

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/templates/TemplatePreviewModal.tsx` around lines 4 - 7, The
TemplatePreviewModal currently calls the prop onUse synchronously allowing
multiple clicks to queue duplicate imports; add a single-flight guard in the
TemplatePreviewModal component: introduce local state (e.g., isSubmitting or
isImporting) and set it true immediately when the CTA handlers invoke onUse,
await the promise returned by onUse (ensure onUse returns a Promise), and only
reset or navigate after it resolves; also disable both CTA buttons and ignore
further clicks while isSubmitting is true to prevent duplicate project creation.
Reference the TemplatePreviewModal component and the onUse prop in
TemplatePreviewModalProps when adding the guard.

Comment on lines +29 to +37
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleUseWithAi();
}
if (e.key === 'Escape') {
onClose();
}
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Escape only closes when the prompt input owns focus.

The dismissal shortcut is wired only through the optional input. When the preview first opens, or when focus is on the close/use buttons, Escape does nothing. Bind the handler at the dialog/window level so keyboard dismissal works consistently.

Also applies to: 73-80

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/templates/TemplatePreviewModal.tsx` around lines 29 - 37, The
Escape key only triggers onClose when the prompt input has focus because
handleKeyDown is currently attached to the input; instead add a
global/dialog-level keydown listener (e.g., in TemplatePreviewModal's root/modal
container or via useEffect on mount) that listens for 'Escape' and calls
onClose, and keep the Enter/Shift-Enter behavior scoped to the prompt input
(leave handleUseWithAi tied to the input's onKeyDown or create a separate
input-specific handler). Ensure you register the listener on mount and remove it
on unmount to avoid leaks and do not change the existing onClose or
handleUseWithAi implementations.

Comment thread src/pages/ProjectPage.tsx
Comment on lines +32 to +50
useEffect(() => {
const prompt = searchParams.get('prompt');
if (prompt && currentProject?.html && !initialPromptSent.current && !streaming) {
initialPromptSent.current = true;
setSearchParams({}, { replace: true });

const chatHistory: Array<{ role: string; content: string }> = [];
addMessage(currentProject.id, 'user', prompt, 'chat');
generate(
currentProject.id,
SYSTEM_PROMPTS.editFull,
buildEditMessages(currentProject.html, chatHistory, prompt)
).then((result) => {
if (result) {
createSnapshot(currentProject.id, result, `Template customization: ${prompt.slice(0, 50)}`);
}
});
}
}, [searchParams, currentProject, streaming, setSearchParams, addMessage, generate, createSnapshot]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Await the auto-customization writes.

This path no longer mirrors handleInlineEdit: addMessage(...) and createSnapshot(...) are fire-and-forget, and a rejected generate(...) promise is never caught. That can persist the assistant response before the user's prompt and leave unhandled rejections during the initial template customization.

Suggested sequencing fix
   useEffect(() => {
     const prompt = searchParams.get('prompt');
-    if (prompt && currentProject?.html && !initialPromptSent.current && !streaming) {
-      initialPromptSent.current = true;
-      setSearchParams({}, { replace: true });
-
-      const chatHistory: Array<{ role: string; content: string }> = [];
-      addMessage(currentProject.id, 'user', prompt, 'chat');
-      generate(
-        currentProject.id,
-        SYSTEM_PROMPTS.editFull,
-        buildEditMessages(currentProject.html, chatHistory, prompt)
-      ).then((result) => {
-        if (result) {
-          createSnapshot(currentProject.id, result, `Template customization: ${prompt.slice(0, 50)}`);
-        }
-      });
-    }
+    if (!prompt || !currentProject?.html || initialPromptSent.current || streaming) return;
+
+    initialPromptSent.current = true;
+    setSearchParams({}, { replace: true });
+
+    void (async () => {
+      try {
+        const chatHistory: Array<{ role: string; content: string }> = [];
+        await addMessage(currentProject.id, 'user', prompt, 'chat');
+        const result = await generate(
+          currentProject.id,
+          SYSTEM_PROMPTS.editFull,
+          buildEditMessages(currentProject.html, chatHistory, prompt)
+        );
+        if (result) {
+          await createSnapshot(
+            currentProject.id,
+            result,
+            `Template customization: ${prompt.slice(0, 50)}`
+          );
+        }
+      } catch (error) {
+        console.error('Failed to apply template customization:', error);
+      }
+    })();
   }, [searchParams, currentProject, streaming, setSearchParams, addMessage, generate, createSnapshot]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const prompt = searchParams.get('prompt');
if (prompt && currentProject?.html && !initialPromptSent.current && !streaming) {
initialPromptSent.current = true;
setSearchParams({}, { replace: true });
const chatHistory: Array<{ role: string; content: string }> = [];
addMessage(currentProject.id, 'user', prompt, 'chat');
generate(
currentProject.id,
SYSTEM_PROMPTS.editFull,
buildEditMessages(currentProject.html, chatHistory, prompt)
).then((result) => {
if (result) {
createSnapshot(currentProject.id, result, `Template customization: ${prompt.slice(0, 50)}`);
}
});
}
}, [searchParams, currentProject, streaming, setSearchParams, addMessage, generate, createSnapshot]);
useEffect(() => {
const prompt = searchParams.get('prompt');
if (!prompt || !currentProject?.html || initialPromptSent.current || streaming) return;
initialPromptSent.current = true;
setSearchParams({}, { replace: true });
void (async () => {
try {
const chatHistory: Array<{ role: string; content: string }> = [];
await addMessage(currentProject.id, 'user', prompt, 'chat');
const result = await generate(
currentProject.id,
SYSTEM_PROMPTS.editFull,
buildEditMessages(currentProject.html, chatHistory, prompt)
);
if (result) {
await createSnapshot(
currentProject.id,
result,
`Template customization: ${prompt.slice(0, 50)}`
);
}
} catch (error) {
console.error('Failed to apply template customization:', error);
}
})();
}, [searchParams, currentProject, streaming, setSearchParams, addMessage, generate, createSnapshot]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/ProjectPage.tsx` around lines 32 - 50, The effect that auto-runs
template customization sends messages and snapshots fire-and-forget and doesn't
handle generate() rejections; update the useEffect block so it awaits the async
sequence: call addMessage(currentProject.id, 'user', prompt, 'chat') if it
returns a promise or ensure it completes, then await generate(currentProject.id,
SYSTEM_PROMPTS.editFull, buildEditMessages(...)) inside a try/catch, and only on
success await createSnapshot(currentProject.id, result, `Template customization:
${prompt.slice(0,50)}`); also reset initialPromptSent.current on errors to allow
retries and clear or handle promise rejections to avoid unhandled rejections
(refer to useEffect, initialPromptSent, addMessage, generate, createSnapshot,
buildEditMessages).

Comment on lines +36 to +42
const handleUseTemplate = async (templateId: string, customPrompt?: string) => {
const tpl = templates.find((t) => t.id === templateId);
if (!tpl) return;

const project = await createProject(tpl.name, tpl.category);
await updateProjectHtml(project.id, tpl.html);
setCurrentProject({ ...project, html: tpl.html });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make the template-to-project write atomic.

This flow commits createProject(...) before the template HTML is stored. If updateProjectHtml(...) rejects, the user is left with a blank project row even though this action is supposed to create a pre-populated project. Please move this into a single store/database operation or transaction.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/TemplatesPage.tsx` around lines 36 - 42, The current
handleUseTemplate flow calls createProject(...) then updateProjectHtml(...),
which can leave a blank project if the second call fails; change this to an
atomic operation by adding a single server-side API (e.g.,
createProjectWithHtml) or a transactional backend method that creates the
project and writes tpl.html in one DB transaction, then call that from
handleUseTemplate and only call setCurrentProject after the atomic call returns;
if a server change isn’t possible, implement client-side rollback in
handleUseTemplate: wrap updateProjectHtml(...) in try/catch and on failure call
deleteProject(project.id) to clean up and rethrow/report the error so the UI
doesn’t show a blank project.

Szymon0C and others added 11 commits April 3, 2026 14:51
… cleanup

- Add Rust models module with download_model, check_model_exists, get_model_path commands
- Add ModelSetup component for first-time AI model selection and download
- Add download progress tracking via Tauri events in ChatPanel
- Auto-detect existing models and start sidecar on app load
- Fallback to /v1/models health check when /health is unavailable
- Update llama.cpp sidecar to b7472, fix extraction path in setup script
- Fix ProjectPage to always reload project by ID on navigation
- Improve PreviewFrame empty state with better messaging
- Remove all debug console.log statements and debug borders
- Fix TypeScript String→string types in ModelInfo interface
- Fix event listener cleanup on download error

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…structure

- Add badges (stars, platform, Tauri, license)
- Add Getting Started section with full dev setup instructions
- Add architecture diagram (How It Works)
- Add project structure tree
- Add Contributing section
- Clean tech stack table
- Remove debug AI setup section (handled in-app now)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Szymon0C Szymon0C changed the title feat: Phase 4 — Templates Gallery feat: Phase 4-5 — Templates, Deploy, Model Setup, README Apr 3, 2026
@Szymon0C Szymon0C merged commit 42e3b8d into main Apr 3, 2026
1 check passed
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