diff --git a/.planning/phases/28-node-defaults-ui/28-01-PLAN.md b/.planning/phases/28-node-defaults-ui/28-01-PLAN.md
new file mode 100644
index 00000000..bda7ba2d
--- /dev/null
+++ b/.planning/phases/28-node-defaults-ui/28-01-PLAN.md
@@ -0,0 +1,270 @@
+---
+phase: 28-node-defaults-ui
+plan: 01
+type: execute
+---
+
+
+Create a settings panel for configuring default node preferences per node type.
+
+Purpose: Users can set their preferred default models for GenerateImage, GenerateVideo, and LLM nodes, so keyboard shortcuts (Shift+G, Shift+L) and quick-add create nodes with their preferred settings.
+Output: New "Node Defaults" tab in ProjectSetupModal with model selection UI for each node type.
+
+
+
+~/.claude/get-shit-done/workflows/execute-phase.md
+~/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/27-node-defaults-infrastructure/27-01-SUMMARY.md
+
+**Key files:**
+@src/components/ProjectSetupModal.tsx
+@src/store/utils/localStorage.ts
+@src/types/nodes.ts
+@src/components/modals/ModelSearchDialog.tsx
+@src/components/nodes/LLMGenerateNode.tsx
+
+**Infrastructure from Phase 27:**
+- `NodeDefaultsConfig` type: `{ generateImage?: GenerateImageNodeDefaults, generateVideo?: GenerateVideoNodeDefaults, llm?: LLMNodeDefaults }`
+- `GenerateImageNodeDefaults`: `{ selectedModel?: { provider, modelId, displayName } }`
+- `GenerateVideoNodeDefaults`: `{ selectedModel?: { provider, modelId, displayName } }`
+- `LLMNodeDefaults`: `{ provider?: LLMProvider, model?: LLMModelType, temperature?: number, maxTokens?: number }`
+- `loadNodeDefaults()` / `saveNodeDefaults()` in localStorage.ts
+- `createDefaultNodeData()` already applies these defaults when creating nodes
+
+**Established patterns:**
+- Tab-based UI in ProjectSetupModal (project, providers tabs)
+- Local state pattern: load from store on open, save on "Save" button
+- Model selection: ModelSearchDialog opens in callback mode via `onModelSelected`
+- LLM settings: dropdowns for provider/model, slider for temperature
+
+
+
+
+
+ Task 1: Add Node Defaults tab to ProjectSetupModal
+ src/components/ProjectSetupModal.tsx
+
+1. Add "Node Defaults" as a third tab after "Providers":
+ - Tab button: `setActiveTab("nodeDefaults")`
+ - activeTab state now `"project" | "providers" | "nodeDefaults"`
+
+2. Add local state for node defaults:
+ - `const [localNodeDefaults, setLocalNodeDefaults] = useState({});`
+ - Import `NodeDefaultsConfig, loadNodeDefaults, saveNodeDefaults` from localStorage.ts
+ - On modal open (in useEffect): `setLocalNodeDefaults(loadNodeDefaults());`
+
+3. Add state for model selection dialogs:
+ - `const [showImageModelDialog, setShowImageModelDialog] = useState(false);`
+ - `const [showVideoModelDialog, setShowVideoModelDialog] = useState(false);`
+
+4. Add handler for saving node defaults:
+ ```typescript
+ const handleSaveNodeDefaults = () => {
+ saveNodeDefaults(localNodeDefaults);
+ onClose();
+ };
+ ```
+
+5. Update handleSave to route to handleSaveNodeDefaults when activeTab === "nodeDefaults"
+
+6. Create Node Defaults tab content (render when activeTab === "nodeDefaults"):
+ - Three sections: GenerateImage, GenerateVideo, LLM
+ - Each in a styled container matching provider tab style (p-3 bg-neutral-900 rounded-lg border border-neutral-700)
+
+**GenerateImage section:**
+- Label: "Default Image Model"
+- Display current selection or "System default (Gemini nano-banana-pro)" if not set
+- "Select Model" button that opens ModelSearchDialog with onModelSelected callback
+- "Clear" button (only shown if selection exists) to reset to undefined
+
+**GenerateVideo section:**
+- Label: "Default Video Model"
+- Display current selection or "None set (select model on first use)" if not set
+- "Select Model" button that opens ModelSearchDialog with initialCapabilityFilter="video"
+- "Clear" button to reset
+
+**LLM section:**
+- Label: "Default LLM Settings"
+- Provider dropdown (Google, OpenAI) - use same PROVIDERS array as LLMGenerateNode
+- Model dropdown (filtered by provider) - use same MODELS object as LLMGenerateNode
+- Temperature slider (0-2, step 0.1) with label showing current value
+- Display "Using system defaults" text if no overrides set
+- "Clear" button to reset all LLM defaults
+
+7. Handle model selection callbacks:
+ ```typescript
+ const handleImageModelSelected = (model: ProviderModel) => {
+ setLocalNodeDefaults(prev => ({
+ ...prev,
+ generateImage: {
+ ...prev.generateImage,
+ selectedModel: {
+ provider: model.provider,
+ modelId: model.id,
+ displayName: model.name,
+ }
+ }
+ }));
+ setShowImageModelDialog(false);
+ };
+ ```
+ Similar for video model.
+
+8. Import ProviderModel from @/lib/providers/types
+
+Do NOT add new imports for ModelSearchDialog - it's already used in FloatingActionBar, so check if we need portal or can render inline. Use portal pattern matching existing modal usage.
+
+ npm run build passes, modal opens with three tabs, Node Defaults tab shows three sections
+ ProjectSetupModal has Node Defaults tab with sections for GenerateImage, GenerateVideo, and LLM defaults
+
+
+
+ Task 2: Wire up model selection dialogs and LLM controls
+ src/components/ProjectSetupModal.tsx
+
+1. Add ModelSearchDialog renders (conditionally when showImageModelDialog/showVideoModelDialog is true):
+ ```tsx
+ {showImageModelDialog && (
+ setShowImageModelDialog(false)}
+ onModelSelected={handleImageModelSelected}
+ initialCapabilityFilter="image"
+ />
+ )}
+ {showVideoModelDialog && (
+ setShowVideoModelDialog(false)}
+ onModelSelected={handleVideoModelSelected}
+ initialCapabilityFilter="video"
+ />
+ )}
+ ```
+
+2. Implement LLM settings handlers:
+ ```typescript
+ const handleLLMProviderChange = (e: React.ChangeEvent) => {
+ const newProvider = e.target.value as LLMProvider;
+ const firstModelForProvider = MODELS[newProvider][0].value;
+ setLocalNodeDefaults(prev => ({
+ ...prev,
+ llm: {
+ ...prev.llm,
+ provider: newProvider,
+ model: firstModelForProvider,
+ }
+ }));
+ };
+
+ const handleLLMModelChange = (e: React.ChangeEvent) => {
+ setLocalNodeDefaults(prev => ({
+ ...prev,
+ llm: { ...prev.llm, model: e.target.value as LLMModelType }
+ }));
+ };
+
+ const handleLLMTemperatureChange = (e: React.ChangeEvent) => {
+ setLocalNodeDefaults(prev => ({
+ ...prev,
+ llm: { ...prev.llm, temperature: parseFloat(e.target.value) }
+ }));
+ };
+ ```
+
+3. Implement clear handlers:
+ ```typescript
+ const handleClearImageDefaults = () => {
+ setLocalNodeDefaults(prev => {
+ const { generateImage, ...rest } = prev;
+ return rest;
+ });
+ };
+ // Similar for video and LLM
+ ```
+
+4. Import PROVIDERS and MODELS arrays - copy from LLMGenerateNode.tsx (they're not exported). Place at top of file:
+ ```typescript
+ const LLM_PROVIDERS: { value: LLMProvider; label: string }[] = [
+ { value: "google", label: "Google" },
+ { value: "openai", label: "OpenAI" },
+ ];
+
+ const LLM_MODELS: Record = {
+ google: [
+ { value: "gemini-3-flash-preview", label: "Gemini 3 Flash" },
+ { value: "gemini-2.5-flash", label: "Gemini 2.5 Flash" },
+ { value: "gemini-3-pro-preview", label: "Gemini 3.0 Pro" },
+ ],
+ openai: [
+ { value: "gpt-4.1-mini", label: "GPT-4.1 Mini" },
+ { value: "gpt-4.1-nano", label: "GPT-4.1 Nano" },
+ ],
+ };
+ ```
+
+5. Import types: LLMProvider, LLMModelType, NodeDefaultsConfig from @/types
+
+6. Complete the UI for each section with proper styling:
+ - Use flex layout with justify-between for label and controls
+ - Model display shows provider icon + model name
+ - Buttons: "Select Model" (primary), "Clear" (text button, neutral-400)
+
+ npm run build passes, can select models from dialogs, LLM dropdowns work, clear buttons reset to defaults
+ Model selection dialogs open and save selections, LLM controls update local state, clear buttons reset defaults
+
+
+
+ Node Defaults tab in Project Settings with:
+ - GenerateImage section: shows selected model or "System default", Select Model and Clear buttons
+ - GenerateVideo section: shows selected model or "None set", Select Model and Clear buttons
+ - LLM section: provider dropdown, model dropdown, temperature slider, Clear button
+ - Changes persist to localStorage and affect new node creation via keyboard shortcuts
+
+ 1. Run: npm run dev
+ 2. Open Project Settings (gear icon in header)
+ 3. Click "Node Defaults" tab (third tab)
+ 4. Verify three sections appear: Image Model, Video Model, LLM Settings
+ 5. Click "Select Model" for Image - verify ModelSearchDialog opens with image filter
+ 6. Select a model (e.g., fal.ai flux) - verify it shows in the section
+ 7. Click "Save" to close modal
+ 8. Press Shift+G to create GenerateImage node - verify it has the selected model
+ 9. Re-open settings, go to Node Defaults, click "Clear" on Image defaults
+ 10. Click "Save", press Shift+G - verify node uses system default (Gemini)
+ 11. Test LLM defaults: change provider to OpenAI, select GPT-4.1 Mini
+ 12. Save and press Shift+L - verify LLM node uses OpenAI provider
+
+ Type "approved" to continue, or describe issues to fix
+
+
+
+
+
+Before declaring plan complete:
+- [ ] `npm run build` succeeds without TypeScript errors
+- [ ] ProjectSetupModal has three tabs: Project, Providers, Node Defaults
+- [ ] Can select default image model from ModelSearchDialog
+- [ ] Can select default video model from ModelSearchDialog
+- [ ] Can configure LLM provider, model, and temperature
+- [ ] Clear buttons reset each section to undefined/system defaults
+- [ ] Settings persist across modal close/reopen
+- [ ] New nodes created via keyboard shortcuts use stored defaults
+
+
+
+
+- All tasks completed
+- TypeScript builds without errors
+- Node Defaults UI is functional and persists settings
+- User verified functionality via checkpoint
+
+
+