feat: Phase 4-5 — Templates, Deploy, Model Setup, README#6
Conversation
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>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (20)
WalkthroughAdds 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
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 IGNOREinside a transaction instead of the globalCOUNT(*)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
loadTemplatesfunction logs errors but doesn't expose error state to consumers. Consider adding anerrorfield 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, oruseOptimisticthat 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
useActionStatefor template loading in the store- Use
useOptimisticfor 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:
- Session storage: Store the prompt in sessionStorage and pass only a flag
- Request body: Use React Router's
stateprop to pass the prompt- 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().stateinstead ofsearchParams.🤖 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
📒 Files selected for processing (9)
docs/superpowers/plans/2026-04-02-phase4-templates.mdsrc/components/templates/TemplateCard.tsxsrc/components/templates/TemplatePreviewModal.tsxsrc/db/database.tssrc/lib/bundledTemplates.tssrc/pages/HomePage.tsxsrc/pages/ProjectPage.tsxsrc/pages/TemplatesPage.tsxsrc/stores/templateStore.ts
| 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]); |
There was a problem hiding this comment.
Address potential race conditions in auto-prompt logic.
The current implementation has several timing issues:
- Ref never resets:
initialPromptSentis never reset, preventing re-execution even for legitimate cases (e.g., navigating to a different project with a prompt) - Early param clearing:
setSearchParams(line 736) clears the prompt beforegenerate()completes—if generation fails, the prompt is lost - Missing cleanup: The
.then()callback executes even if the component unmounts - Weak streaming check: The
!streamingcondition (line 734) doesn't prevent race conditions sincegenerate()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.
| interface TemplatePreviewModalProps { | ||
| template: Template; | ||
| onUse: (templateId: string, customPrompt?: string) => void; | ||
| onClose: () => void; |
There was a problem hiding this comment.
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.
| const handleKeyDown = (e: React.KeyboardEvent) => { | ||
| if (e.key === 'Enter' && !e.shiftKey) { | ||
| e.preventDefault(); | ||
| handleUseWithAi(); | ||
| } | ||
| if (e.key === 'Escape') { | ||
| onClose(); | ||
| } | ||
| }; |
There was a problem hiding this comment.
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.
| 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]); |
There was a problem hiding this comment.
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.
| 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).
| 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 }); |
There was a problem hiding this comment.
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.
… 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>
Summary
?prompt=query param)/v1/models, llama.cpp upgraded to b7472New files
src-tauri/src/deploy.rs— Rust deploy commands (Netlify, Vercel, GitHub Pages, export)src-tauri/src/models.rs— Model download, existence check, path resolutionsrc/components/deploy/DeployModal.tsx— Deploy modal with provider selectionsrc/components/chat/ModelSetup.tsx— First-time AI model setup UIsrc/components/templates/TemplateCard.tsx— Template card with iframe previewsrc/components/templates/TemplatePreviewModal.tsx— Template preview + AI customizationsrc/stores/deployStore.ts— Deploy state + token persistencesrc/stores/templateStore.ts— Template state + filteringsrc/lib/deployProviders.ts— Provider metadatasrc/lib/bundledTemplates.ts— 4 bundled HTML templatesTest plan
npx tsc --noEmit)npm run build)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Chores