Viber’s surgical edit system makes precise changes to existing code while preserving all unmodified content. Unlike regenerating entire files, surgical edits update only what’s necessary.
How surgical edits work
Surgical editing is enabled by Viber’s component-based architecture combined with context-aware AI prompting:
User requests edit
User says: “Make the header background blue”
Identify target files
Viber determines which file(s) need editing:
“header” → src/components/Header.tsx
“hero” → src/components/Hero.tsx
“footer” → src/components/Footer.tsx
Read current content
Viber reads the current file contents from the sandbox: const fileContents = await getSandboxFileContents ([
"src/components/Header.tsx"
], sandboxId );
Generate with context
AI receives the edit prompt PLUS current file contents: const systemPrompt = buildSystemPrompt ( true , fileContents );
AI makes surgical change
AI outputs the COMPLETE file with ONLY the requested change: < file path = "src/components/Header.tsx" >
// Same imports
// Same structure
// Changed: bg-gray-900 → bg-blue-500
// Everything else preserved
</ file >
Apply to sandbox
Updated file overwrites the old version, HMR updates preview instantly.
Component-based architecture
Surgical edits rely on breaking apps into separate component files:
✅ Good: Modular components
❌ Bad: Monolithic component
// src/App.tsx
import Header from "./components/Header" ;
import Hero from "./components/Hero" ;
import Features from "./components/Features" ;
function App () {
return (
< div >
< Header />
< Hero />
< Features />
</ div >
);
}
Why modular? If header is in Header.tsx, editing the header only touches that one file. If everything is in App.tsx, editing the header requires regenerating the entire app.
Edit mode prompt
The AI receives special instructions for surgical editing:
KEY PRINCIPLES (CRITICAL):
1. **Minimal Changes**: Only modify what's necessary - preserve 99% of existing code
2. **Preserve Functionality**: Keep all existing features, imports, and structure
3. **Respect Structure**: Follow existing patterns and code style
4. **Target Precision**: Edit specific files/components, not everything
5. **Context Awareness**: Use imports/exports to understand component relationships
EDIT STRATEGY EXAMPLES:
### Example 1: Update Single Style (CRITICAL)
USER: "update the hero to bg blue"
CORRECT APPROACH:
1. Identify Hero component: src/components/Hero.tsx
2. Locate the background color class (e.g., 'bg-gray-900')
3. Replace ONLY that class with 'bg-blue-500'
4. Return the ENTIRE file unchanged except for that single class
INCORRECT APPROACH:
- Regenerating entire file
- Changing other styles
- Reformatting or "improving" code
- Modifying unrelated components
The prompt explicitly shows the AI what NOT to do, preventing over-eager “improvements” that break existing code.
File context injection
Viber injects current file contents into the AI prompt:
export function buildSystemPrompt (
isEdit : boolean ,
fileContext ?: Record < string , string >
) : string {
let prompt = isEdit ? EDIT_MODE_PROMPT : INITIAL_GENERATION_PROMPT ;
if ( fileContext && Object . keys ( fileContext ). length > 0 ) {
prompt += FILE_CONTEXT_PROMPT ;
for ( const [ path , content ] of Object . entries ( fileContext )) {
if ( content . length < 5000 ) {
prompt += ` \n <file path=" ${ path } "> \n ${ content } \n </file> \n ` ;
} else {
prompt += ` \n <file path=" ${ path } ">[File too large - ${ content . length } chars]</file> \n ` ;
}
}
}
return prompt ;
}
This gives the AI:
The exact current state of each file
All imports and dependencies
Existing code structure and style
Context about what should NOT change
Edit examples
Example 1: Style change
User request
Before
After
“Make the header background blue”
src/components/Header.tsx
import { Menu } from "lucide-react" ;
export default function Header () {
return (
< header className = "bg-gray-900 text-white px-6 py-4" >
< div className = "flex items-center justify-between" >
< h1 className = "text-2xl font-bold" > My App </ h1 >
< button >
< Menu className = "size-6" />
</ button >
</ div >
</ header >
);
}
src/components/Header.tsx
import { Menu } from "lucide-react" ;
export default function Header () {
return (
< header className = "bg-blue-600 text-white px-6 py-4" >
< div className = "flex items-center justify-between" >
< h1 className = "text-2xl font-bold" > My App </ h1 >
< button >
< Menu className = "size-6" />
</ button >
</ div >
</ header >
);
}
Changed: bg-gray-900 → bg-blue-600Preserved: Imports, structure, text, all other styles
Example 2: Add element
User request
Before
After
“Add a search bar to the header”
src/components/Header.tsx
import { Menu } from "lucide-react" ;
export default function Header () {
return (
< header className = "bg-gray-900 text-white px-6 py-4" >
< div className = "flex items-center justify-between" >
< h1 className = "text-2xl font-bold" > My App </ h1 >
< button >
< Menu className = "size-6" />
</ button >
</ div >
</ header >
);
}
src/components/Header.tsx
import { Menu , Search } from "lucide-react" ;
export default function Header () {
return (
< header className = "bg-gray-900 text-white px-6 py-4" >
< div className = "flex items-center justify-between" >
< h1 className = "text-2xl font-bold" > My App </ h1 >
< div className = "flex items-center gap-4" >
< div className = "relative" >
< input
type = "text"
placeholder = "Search..."
className = "bg-gray-800 text-white px-4 py-2 pr-10 rounded-lg"
/>
< Search className = "size-5 absolute right-3 top-1/2 -translate-y-1/2 text-gray-400" />
</ div >
< button >
< Menu className = "size-6" />
</ button >
</ div >
</ div >
</ header >
);
}
Changed:
Added Search to imports
Added search input with icon
Wrapped button in flex container
Preserved: Header structure, branding, menu button, all styles
Example 3: Remove element
User request
Before
After
“Remove the menu button from the header”
src/components/Header.tsx
import { Menu } from "lucide-react" ;
export default function Header () {
return (
< header className = "bg-gray-900 text-white px-6 py-4" >
< div className = "flex items-center justify-between" >
< h1 className = "text-2xl font-bold" > My App </ h1 >
< button >
< Menu className = "size-6" />
</ button >
</ div >
</ header >
);
}
src/components/Header.tsx
export default function Header () {
return (
< header className = "bg-gray-900 text-white px-6 py-4" >
< div className = "flex items-center" >
< h1 className = "text-2xl font-bold" > My App </ h1 >
</ div >
</ header >
);
}
Changed:
Removed Menu import (unused)
Removed button element
Simplified flex container
Preserved: Header structure, branding, styles
File identification
Viber maps user intent to specific files:
header → src/components/Header.tsx
navigation / nav → src/components/Nav.tsx or Header.tsx
hero → src/components/Hero.tsx
footer → src/components/Footer.tsx
features → src/components/Features.tsx
testimonials → src/components/Testimonials.tsx
about → src/components/About.tsx
global styles → src/index.css
If the file doesn’t exist (e.g., no Testimonials.tsx), the AI may create it or suggest it can’t find the component.
Reading sandbox files
Before editing, Viber fetches current file contents:
// In generation flow
if ( isEdit && sandboxId ) {
const fileList = await getSandboxFileList ( sandboxId );
// Read all .tsx and .ts files for context
const relevantFiles = fileList . filter ( f =>
f . endsWith ( ".tsx" ) || f . endsWith ( ".ts" )
);
const fileContext = await getSandboxFileContents (
relevantFiles ,
sandboxId
);
// Pass to AI
await generateCode ({
prompt ,
isEdit: true ,
fileContext: fileContext . files ,
});
}
This ensures the AI sees the current state before making changes.
Preserving imports
One critical aspect of surgical edits is import hygiene:
✅ Good: Update imports when adding elements
✅ Good: Remove unused imports
❌ Bad: Missing import
// Before
import { Menu } from "lucide-react" ;
// After (added Search icon)
import { Menu , Search } from "lucide-react" ;
Viber’s prompts explicitly remind the AI: “IMPORT HYGIENE: When adding new elements, add corresponding imports at the top.”
Multi-file edits
Some edits require changing multiple files:
Example: Add new component to existing page
User: “Add a newsletter signup to the footer”AI outputs: < file path = "src/components/Newsletter.tsx" >
// New component
export default function Newsletter() {
return (
< div className = "bg-gray-100 p-6 rounded-lg" >
< h3 className = "text-xl font-bold mb-2" > Subscribe to our newsletter </ h3 >
< input type = "email" placeholder = "Your email" />
< button > Subscribe </ button >
</ div >
);
}
</ file >
< file path = "src/components/Footer.tsx" >
import Newsletter from "./Newsletter";
// ... existing Footer code ...
// Added Newsletter component inside Footer
</ file >
Result: Newsletter.tsx created, Footer.tsx updated to import and render it.
Edge cases
If user asks to edit a file that doesn’t exist: User: "Update the pricing section"
If Pricing.tsx doesn’t exist, AI may:
Create it from scratch
Explain it doesn’t exist
Ask for clarification
If the request is unclear: AI may:
Ask which component
Ask which color (text, background, border)
Make a best guess based on recent context
If the request requires major changes: User: "Rebuild the entire header with a new design"
This becomes a full regeneration, not a surgical edit. The AI recreates Header.tsx from scratch.
Limitations
File size limits: Files larger than 5000 characters are truncated when passed to the AI. For very large files, surgical edits may not have full context.
Token limits: If the project has many files, not all can fit in the context window. Viber prioritizes relevant files based on the edit request.
Complexity: Very complex edits (e.g., “Refactor to use a different state management library”) may require manual intervention.
Best practices
Be specific “Make the header background blue” is better than “Change the header.”
Reference components Use component names: “Edit the Hero” instead of “Change the top section.”
One change at a time “Make the header blue, add a search bar, and remove the logo” is harder than 3 separate requests.
Check the preview After each edit, verify the change looks correct before the next edit.
Use voice naturally Voice interaction handles context better. Say: “Now make it darker” after “Make the header blue.”
Modular structure Keep components small and focused. Easier to edit Hero.tsx than a 500-line App.tsx.
Debugging failed edits
If an edit doesn’t work as expected:
Check the code view
Look at the generated code to see what actually changed.
Look for console errors
Open browser DevTools to check for import errors or runtime errors.
Try a more specific prompt
Rephrase: “Change the background of the header to blue-600” instead of “Make it blue.”
Regenerate if needed
Say: “Rebuild the header component” to start fresh.
Comparing to full regeneration
Surgical edit
Full regeneration
Pros:
Fast (only edits one file)
Preserves all unchanged code
Maintains user customizations
Less prone to introducing bugs
Cons:
May not handle large refactors well
Requires good component modularity
Pros:
Can handle complex redesigns
Fresh start if code is broken
AI has full creative freedom
Cons:
Slow (regenerates all files)
Loses user customizations
May introduce new bugs
More token usage
Viber defaults to surgical edits whenever possible. Say “start over” or “rebuild everything” to force full regeneration.