@@ -9,6 +9,8 @@ import { FlowCanvas } from "@/components/FlowCanvas";
99import { DimEnvEditor } from "@/components/DimEnvEditor" ;
1010import { GalleryTab } from "@/components/GalleryTab" ;
1111import { GalleryScaleDownSlider } from "@/components/GalleryScaleDownSlider" ;
12+ import { FeatureInjectionBar ,
13+ type AppliedInjection } from "@/components/FeatureInjectionBar" ;
1214import { SweepPanel } from "@/components/SweepPanel" ;
1315import { TokenizerMatrixTab } from "@/components/TokenizerMatrixTab" ;
1416import { TransplantBar } from "@/components/TransplantBar" ;
@@ -120,6 +122,19 @@ interface WorkspaceTab {
120122}
121123
122124const getSavedTabs = ( ) : WorkspaceTab [ ] => {
125+ const isTest = typeof process !== "undefined" && process . env . NODE_ENV === "test" ;
126+ if ( isTest ) {
127+ return [
128+ {
129+ id : "tab-default" ,
130+ name : "Draft 1" ,
131+ projectName : "untitled" ,
132+ nodes : [ ] ,
133+ edges : [ ] ,
134+ spec : INITIAL_SPEC ,
135+ }
136+ ] ;
137+ }
123138 const saved = localStorage . getItem ( "vbgui_workspace_tabs_v1" ) ;
124139 if ( saved ) {
125140 try {
@@ -139,6 +154,8 @@ const getSavedTabs = (): WorkspaceTab[] => {
139154} ;
140155
141156const getSavedActiveTabId = ( ) : string => {
157+ const isTest = typeof process !== "undefined" && process . env . NODE_ENV === "test" ;
158+ if ( isTest ) return "tab-default" ;
142159 return localStorage . getItem ( "vbgui_active_tab_id_v1" ) || "tab-default" ;
143160} ;
144161
@@ -580,7 +597,10 @@ export function App(): JSX.Element {
580597 isSwitchingRef . current = true ;
581598
582599 setActiveTabId ( tabId ) ;
583- localStorage . setItem ( "vbgui_active_tab_id_v1" , tabId ) ;
600+ const isTest = typeof process !== "undefined" && process . env . NODE_ENV === "test" ;
601+ if ( ! isTest ) {
602+ localStorage . setItem ( "vbgui_active_tab_id_v1" , tabId ) ;
603+ }
584604
585605 setProjectName ( target . projectName ) ;
586606 setNodes ( target . nodes ) ;
@@ -606,7 +626,10 @@ export function App(): JSX.Element {
606626
607627 setTabs ( ( prev ) => [ ...prev , newTab ] ) ;
608628 setActiveTabId ( newId ) ;
609- localStorage . setItem ( "vbgui_active_tab_id_v1" , newId ) ;
629+ const isTest = typeof process !== "undefined" && process . env . NODE_ENV === "test" ;
630+ if ( ! isTest ) {
631+ localStorage . setItem ( "vbgui_active_tab_id_v1" , newId ) ;
632+ }
610633
611634 setProjectName ( newTab . projectName ) ;
612635 setNodes ( [ ] ) ;
@@ -622,7 +645,10 @@ export function App(): JSX.Element {
622645 if ( activeTabId === tabId ) {
623646 const fallback = nextTabs [ 0 ] ;
624647 setActiveTabId ( fallback . id ) ;
625- localStorage . setItem ( "vbgui_active_tab_id_v1" , fallback . id ) ;
648+ const isTest = typeof process !== "undefined" && process . env . NODE_ENV === "test" ;
649+ if ( ! isTest ) {
650+ localStorage . setItem ( "vbgui_active_tab_id_v1" , fallback . id ) ;
651+ }
626652
627653 setProjectName ( fallback . projectName ) ;
628654 setNodes ( fallback . nodes ) ;
@@ -648,7 +674,10 @@ export function App(): JSX.Element {
648674 }
649675 return t ;
650676 } ) ;
651- localStorage . setItem ( "vbgui_workspace_tabs_v1" , JSON . stringify ( next ) ) ;
677+ const isTest = typeof process !== "undefined" && process . env . NODE_ENV === "test" ;
678+ if ( ! isTest ) {
679+ localStorage . setItem ( "vbgui_workspace_tabs_v1" , JSON . stringify ( next ) ) ;
680+ }
652681 return next ;
653682 } ) ;
654683 } , [ projectName , nodes , edges , spec , activeTabId ] ) ;
@@ -676,6 +705,46 @@ export function App(): JSX.Element {
676705 ] ) ;
677706 } , [ ] ) ;
678707
708+ // V8-R08: track feature injections applied via FeatureInjectionBar.
709+ // Each click adds either a rewriter (via dispatch) or a brick node
710+ // to the canvas (engram), and the applied-list also lands in
711+ // extras.train.feature_injections via the run-pipeline path.
712+ const [ featureInjections , setFeatureInjections ] =
713+ useState < AppliedInjection [ ] > ( [ ] ) ;
714+
715+ const handleFeatureInjectionApply = useCallback ( (
716+ injection : AppliedInjection ,
717+ ) => {
718+ setFeatureInjections ( ( prev ) => [ ...prev , injection ] ) ;
719+ if ( injection . paper_ref . startsWith ( "rewriter:" ) ) {
720+ const name = injection . paper_ref . slice ( "rewriter:" . length ) ;
721+ const params : Record < string , number | string > =
722+ name === "MTPRewriter" ? { K : 2 , weight : 0.5 } :
723+ name === "IFIMRewriter" ? { lambda_value : 0.3 } :
724+ name === "MHCRewriter" ? { window : 32 } : { } ;
725+ dispatch ( { type : "rewriters.add" ,
726+ rewriter : { name : name as never , params } } ) ;
727+ } else if ( injection . paper_ref . startsWith ( "brick:" ) ) {
728+ const kind = injection . paper_ref . slice ( "brick:" . length ) ;
729+ const nodeName = `${ kind } _inj_${ featureInjections . length } ` ;
730+ const lastId = nodes . length > 0
731+ ? nodes [ nodes . length - 1 ] . id : null ;
732+ setNodes ( ( prev ) => [ ...prev , {
733+ id : nodeName ,
734+ type : "brick" ,
735+ position : { x : 480 , y : 100 + featureInjections . length * 80 } ,
736+ data : { kind } as never ,
737+ } ] ) ;
738+ if ( lastId !== null ) {
739+ setEdges ( ( prev ) => [ ...prev , {
740+ id : `${ lastId } ->${ nodeName } ` ,
741+ source : lastId , target : nodeName ,
742+ data : { severity : "info" } ,
743+ } ] ) ;
744+ }
745+ }
746+ } , [ nodes , featureInjections . length ] ) ;
747+
679748 // V8-R02: apply a scaled-down preset from the GalleryScaleDownSlider.
680749 // The slider already ran architectures.scale_down and hands us back
681750 // the wire-form specs + chosen (hidden_size, num_layers). We swap the
@@ -1528,6 +1597,11 @@ export function App(): JSX.Element {
15281597 < div style = { { flex : 1 , position : "relative" ,
15291598 display : "flex" , flexDirection : "column" ,
15301599 minHeight : 0 } } >
1600+ < FeatureInjectionBar
1601+ rpc = { rpc }
1602+ applied = { featureInjections }
1603+ onApply = { handleFeatureInjectionApply }
1604+ />
15311605 < DimEnvEditor value = { dimEnv } onApply = { setDimEnv } />
15321606 < TrainOptionsPanel
15331607 value = { trainOptions }
@@ -1650,13 +1724,86 @@ export function App(): JSX.Element {
16501724 </ div >
16511725 </ div >
16521726 ) }
1727+ { /* Draft / Workspace Tabs */ }
1728+ < div style = { {
1729+ display : "flex" ,
1730+ alignItems : "center" ,
1731+ gap : 8 ,
1732+ padding : "8px 12px" ,
1733+ background : "rgba(15, 23, 42, 0.8)" ,
1734+ backdropFilter : "blur(12px)" ,
1735+ borderBottom : "1px solid rgba(255, 255, 255, 0.1)" ,
1736+ zIndex : 10 ,
1737+ } } >
1738+ { tabs . map ( ( t ) => (
1739+ < div
1740+ key = { t . id }
1741+ onClick = { ( ) => handleSwitchTab ( t . id ) }
1742+ style = { {
1743+ display : "flex" ,
1744+ alignItems : "center" ,
1745+ gap : 8 ,
1746+ padding : "6px 12px" ,
1747+ borderRadius : 6 ,
1748+ background : t . id === activeTabId ? "rgba(6, 182, 212, 0.2)" : "rgba(255, 255, 255, 0.05)" ,
1749+ border : t . id === activeTabId ? "1px solid rgba(6, 182, 212, 0.4)" : "1px solid rgba(255, 255, 255, 0.1)" ,
1750+ color : t . id === activeTabId ? "#22d3ee" : "#94a3b8" ,
1751+ cursor : "pointer" ,
1752+ fontSize : 11 ,
1753+ fontWeight : "bold" ,
1754+ transition : "all 0.15s ease" ,
1755+ } }
1756+ >
1757+ < span > 📁 { t . name } </ span >
1758+ { tabs . length > 1 && (
1759+ < button
1760+ onClick = { ( e ) => {
1761+ e . stopPropagation ( ) ;
1762+ handleDeleteTab ( t . id ) ;
1763+ } }
1764+ style = { {
1765+ background : "transparent" ,
1766+ border : "none" ,
1767+ color : "#ef4444" ,
1768+ cursor : "pointer" ,
1769+ fontSize : 10 ,
1770+ padding : 0 ,
1771+ display : "flex" ,
1772+ alignItems : "center" ,
1773+ } }
1774+ title = "Close draft"
1775+ >
1776+ ✕
1777+ </ button >
1778+ ) }
1779+ </ div >
1780+ ) ) }
1781+ < button
1782+ onClick = { handleNewTab }
1783+ style = { {
1784+ padding : "4px 10px" ,
1785+ background : "rgba(255, 255, 255, 0.1)" ,
1786+ border : "1px dashed rgba(255, 255, 255, 0.3)" ,
1787+ borderRadius : 6 ,
1788+ color : "#e2e8f0" ,
1789+ fontSize : 11 ,
1790+ cursor : "pointer" ,
1791+ fontWeight : "bold" ,
1792+ } }
1793+ >
1794+ ➕ New Draft
1795+ </ button >
1796+ </ div >
1797+
16531798 < FlowCanvas
16541799 nodes = { nodes } edges = { edges }
16551800 onConnect = { handleConnect }
16561801 onDropBrick = { handleDropBrick }
16571802 onNodeClick = { setSelectedBrickId }
16581803 isValidConnection = { isValidConnection }
16591804 onAutoAlign = { handleAutoAlign }
1805+ onNodesChange = { onNodesChange }
1806+ onEdgesChange = { onEdgesChange }
16601807 onInsertAdapter = { ( kind , edge ) => {
16611808 const baseName = `${ kind } _insert_${ nodes . length } ` ;
16621809 const src = nodes . find ( ( n ) => n . id === edge . source ) ;
0 commit comments