@buoy-gg/scenarios 0.0.0-stage → 7.0.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +78 -2
  3. package/lib/commonjs/catalog.js +1 -0
  4. package/lib/commonjs/components/ActiveCard.js +1 -0
  5. package/lib/commonjs/components/DeactivateReportCard.js +1 -0
  6. package/lib/commonjs/components/FolderPickerScreen.js +1 -0
  7. package/lib/commonjs/components/MacroReviewScreen.js +1 -0
  8. package/lib/commonjs/components/PreviewScreen.js +1 -0
  9. package/lib/commonjs/components/RecordingBanner.js +1 -0
  10. package/lib/commonjs/components/RunProgressScreen.js +1 -0
  11. package/lib/commonjs/components/ScenarioBanner.js +1 -0
  12. package/lib/commonjs/components/ScenarioDetailScreen.js +1 -0
  13. package/lib/commonjs/components/ScenarioFolderBar.js +1 -0
  14. package/lib/commonjs/components/ScenarioRow.js +1 -0
  15. package/lib/commonjs/components/ScenariosBody.js +1 -0
  16. package/lib/commonjs/components/ScenariosToolView.js +1 -0
  17. package/lib/commonjs/defineScenario.js +1 -0
  18. package/lib/commonjs/engine.js +1 -0
  19. package/lib/commonjs/folders.js +1 -0
  20. package/lib/commonjs/hooks/useScenarios.js +1 -0
  21. package/lib/commonjs/index.js +1 -0
  22. package/lib/commonjs/interpolate.js +1 -0
  23. package/lib/commonjs/package.json +1 -0
  24. package/lib/commonjs/preset.js +1 -0
  25. package/lib/commonjs/record/condense.js +1 -0
  26. package/lib/commonjs/record/recorderStore.js +1 -0
  27. package/lib/commonjs/record/types.js +1 -0
  28. package/lib/commonjs/store/scenariosStore.js +1 -0
  29. package/lib/commonjs/sync/scenariosSyncAdapter.js +1 -0
  30. package/lib/commonjs/types.js +1 -0
  31. package/lib/commonjs/ui/primitives.js +1 -0
  32. package/lib/commonjs/ui/scnTheme.js +1 -0
  33. package/lib/module/catalog.js +1 -0
  34. package/lib/module/components/ActiveCard.js +1 -0
  35. package/lib/module/components/DeactivateReportCard.js +1 -0
  36. package/lib/module/components/FolderPickerScreen.js +1 -0
  37. package/lib/module/components/MacroReviewScreen.js +1 -0
  38. package/lib/module/components/PreviewScreen.js +1 -0
  39. package/lib/module/components/RecordingBanner.js +1 -0
  40. package/lib/module/components/RunProgressScreen.js +1 -0
  41. package/lib/module/components/ScenarioBanner.js +1 -0
  42. package/lib/module/components/ScenarioDetailScreen.js +1 -0
  43. package/lib/module/components/ScenarioFolderBar.js +1 -0
  44. package/lib/module/components/ScenarioRow.js +1 -0
  45. package/lib/module/components/ScenariosBody.js +1 -0
  46. package/lib/module/components/ScenariosToolView.js +1 -0
  47. package/lib/module/defineScenario.js +1 -0
  48. package/lib/module/engine.js +1 -0
  49. package/lib/module/folders.js +1 -0
  50. package/lib/module/hooks/useScenarios.js +1 -0
  51. package/lib/module/index.js +1 -0
  52. package/lib/module/interpolate.js +1 -0
  53. package/lib/module/preset.js +1 -0
  54. package/lib/module/record/condense.js +1 -0
  55. package/lib/module/record/recorderStore.js +1 -0
  56. package/lib/module/record/types.js +1 -0
  57. package/lib/module/store/scenariosStore.js +1 -0
  58. package/lib/module/sync/scenariosSyncAdapter.js +1 -0
  59. package/lib/module/types.js +1 -0
  60. package/lib/module/ui/primitives.js +1 -0
  61. package/lib/module/ui/scnTheme.js +1 -0
  62. package/lib/typescript/catalog.d.ts +81 -0
  63. package/lib/typescript/components/ActiveCard.d.ts +24 -0
  64. package/lib/typescript/components/DeactivateReportCard.d.ts +15 -0
  65. package/lib/typescript/components/FolderPickerScreen.d.ts +23 -0
  66. package/lib/typescript/components/MacroReviewScreen.d.ts +46 -0
  67. package/lib/typescript/components/PreviewScreen.d.ts +30 -0
  68. package/lib/typescript/components/RecordingBanner.d.ts +30 -0
  69. package/lib/typescript/components/RunProgressScreen.d.ts +21 -0
  70. package/lib/typescript/components/ScenarioBanner.d.ts +33 -0
  71. package/lib/typescript/components/ScenarioDetailScreen.d.ts +20 -0
  72. package/lib/typescript/components/ScenarioFolderBar.d.ts +34 -0
  73. package/lib/typescript/components/ScenarioRow.d.ts +32 -0
  74. package/lib/typescript/components/ScenariosBody.d.ts +26 -0
  75. package/lib/typescript/components/ScenariosToolView.d.ts +13 -0
  76. package/lib/typescript/defineScenario.d.ts +48 -0
  77. package/lib/typescript/engine.d.ts +76 -0
  78. package/lib/typescript/folders.d.ts +51 -0
  79. package/lib/typescript/hooks/useScenarios.d.ts +15 -0
  80. package/lib/typescript/index.d.ts +32 -0
  81. package/lib/typescript/interpolate.d.ts +29 -0
  82. package/lib/typescript/preset.d.ts +56 -0
  83. package/lib/typescript/record/condense.d.ts +131 -0
  84. package/lib/typescript/record/recorderStore.d.ts +63 -0
  85. package/lib/typescript/record/types.d.ts +140 -0
  86. package/lib/typescript/store/scenariosStore.d.ts +166 -0
  87. package/lib/typescript/sync/scenariosSyncAdapter.d.ts +342 -0
  88. package/lib/typescript/types.d.ts +196 -0
  89. package/lib/typescript/ui/primitives.d.ts +74 -0
  90. package/lib/typescript/ui/scnTheme.d.ts +68 -0
  91. package/lib/web/index.mjs +2 -0
  92. package/package.json +100 -3
@@ -0,0 +1 @@
1
+ "use strict";import{StyleSheet as m,Text as l,View as a}from"react-native";import{AlertTriangle as f,Check as g,ToolButton as x}from"@buoy-gg/shared-ui";import{scn as n,SCN_MONO as p}from"../ui/scnTheme";import{Panel as y}from"../ui/primitives";import{jsx as s,jsxs as c}from"react/jsx-runtime";export function DeactivateReportCard({report:e,onDismiss:h}){const t=[];if(e.removedRules){t.push({tone:"ok",text:`${e.removedRules} override rule${e.removedRules===1?"":"s"} removed \u2014 pull to refresh shows real data`})}if(e.stoppedImpersonation){t.push({tone:"ok",text:"Impersonation ended"})}if(e.clearedTransients){t.push({tone:"ok",text:`${e.clearedTransients} one-shot effect${e.clearedTransients===1?"":"s"} had nothing to remove`})}if(e.undoStepsRan){t.push({tone:"ok",text:`${e.undoStepsRan} undo step${e.undoStepsRan===1?"":"s"} ran`})}if(e.unrestoredStorageKeys.length){t.push({tone:"warn",text:`${e.unrestoredStorageKeys.length} storage key${e.unrestoredStorageKeys.length===1?"":"s"} NOT restored: ${e.unrestoredStorageKeys.join(", ")} \u2014 a storage write has no automatic inverse. Re-run your login flow, or restore a Time Machine snapshot.`})}const r=e.unrestoredState??[];if(r.length){t.push({tone:"warn",text:`${r.length} store${r.length===1?"":"s"} still hold${r.length===1?"s":""} what this scenario wrote: ${r.join(", ")} \u2014 a state write has no automatic inverse. Reset it in its own tool, or restore a Time Machine snapshot.`})}for(const o of e.errors){t.push({tone:"warn",text:o})}if(!t.length){t.push({tone:"ok",text:"Nothing left to undo \u2014 the app is real."})}const d=t.every(o=>o.tone==="ok");return s(y,{tone:d?"neutral":"sim",children:c(a,{style:i.body,children:[s(l,{style:i.title,testID:"scenario-deactivate-report",children:"DEACTIVATED"}),t.map((o,u)=>c(a,{style:i.line,children:[o.tone==="ok"?s(g,{size:12,color:n.color.accent}):s(f,{size:12,color:n.color.sim}),s(l,{style:i.text,children:o.text})]},`${o.tone}-${u}`)),s(a,{style:i.actions,children:s(x,{label:"Done",variant:"primary",size:"small",onPress:h,testID:"scenario-dismiss-report"})})]})})}const i=m.create({body:{padding:n.textInset,gap:8},title:{color:n.color.textSecondary,fontSize:n.font.micro,fontWeight:"700",letterSpacing:.8,fontFamily:p},line:{flexDirection:"row",gap:8,alignItems:"flex-start"},text:{flex:1,color:n.color.textSecondary,fontSize:n.font.control,lineHeight:17},actions:{flexDirection:"row",marginTop:2}});
@@ -0,0 +1 @@
1
+ "use strict";import{useState as x}from"react";import{ScrollView as S,StyleSheet as C,Text as f,TextInput as T,TouchableOpacity as L,View as m}from"react-native";import{Bookmark as I,Check as z,Plus as D,ToolButton as H}from"@buoy-gg/shared-ui";import{useToolHeader as F}from"@buoy-gg/core";import{MAX_FOLDER_LENGTH as k,normalizeFolder as u}from"../folders";import{scn as e,SCN_MONO as N}from"../ui/scnTheme";import{Footnote as O,Panel as w,PanelSeparator as P,SectionLabel as h}from"../ui/primitives";import{displayName as R}from"./ScenarioRow";import{jsx as o,jsxs as s}from"react/jsx-runtime";export function FolderPickerScreen({scenario:l,folders:a,onPick:r,onBack:d}){const[c,y]=x("");F({title:"Move to folder",subtitle:R(l.name),onBack:d,searchable:false},[l.id]);const p=u(l.folder);const n=u(c);const b=!!n&&!a.some(i=>i.toLowerCase()===n.toLowerCase());return s(S,{style:t.fill,contentContainerStyle:t.content,keyboardShouldPersistTaps:"handled",testID:"scenario-folder-scroll",children:[o(h,{icon:I,count:a.length,children:"Folders"}),s(w,{children:[o(g,{label:"No folder",hint:"Shows under Ungrouped",selected:!p,onPress:()=>r(void 0),testID:"scenario-folder-none"}),a.map(i=>s(m,{children:[o(P,{}),o(g,{label:i,selected:!!p&&p.toLowerCase()===i.toLowerCase(),onPress:()=>r(i),testID:`scenario-folder-pick-${i.toLowerCase().replace(/\s+/g,"-")}`})]},i))]}),o(h,{icon:D,children:"New folder"}),o(w,{children:s(m,{style:t.newRow,children:[o(T,{style:t.input,value:c,onChangeText:y,placeholder:"Checkout",placeholderTextColor:e.color.textTertiary,maxLength:k,autoCapitalize:"words",autoCorrect:false,returnKeyType:"done",onSubmitEditing:()=>n&&r(n),testID:"scenario-folder-new-input"}),o(H,{label:n&&!b?"Use":"Create",variant:"primary",size:"small",disabled:!n,onPress:()=>n&&r(n),testID:"scenario-folder-create"})]})}),o(O,{children:"A folder exists as long as something is filed in it \u2014 empty it and it goes away. Typing a name that already exists files it there instead of making a second one."})]})}function g({label:l,hint:a,selected:r,onPress:d,testID:c}){return s(L,{style:t.row,onPress:d,activeOpacity:.7,accessibilityRole:"button",accessibilityState:{selected:r},accessibilityLabel:`File under ${l}`,testID:c,children:[s(m,{style:t.rowText,children:[o(f,{style:[t.rowLabel,r&&t.rowLabelSelected],numberOfLines:1,children:l}),a?o(f,{style:t.rowHint,children:a}):null]}),r?o(z,{size:14,color:e.color.accent}):null]})}const t=C.create({fill:{flex:1,backgroundColor:"transparent"},content:{paddingBottom:32},row:{flexDirection:"row",alignItems:"center",gap:12,minHeight:e.rowMinHeight,paddingHorizontal:e.textInset,paddingVertical:10},rowText:{flex:1,minWidth:0},rowLabel:{color:e.color.text,fontSize:e.font.row},rowLabelSelected:{color:e.color.accent,fontWeight:"600"},rowHint:{color:e.color.textTertiary,fontSize:e.font.micro,marginTop:2},newRow:{flexDirection:"row",alignItems:"center",gap:8,paddingHorizontal:e.textInset,paddingVertical:10},input:{flex:1,backgroundColor:e.color.row,borderRadius:e.radius.row,borderWidth:1,borderColor:e.color.border,color:e.color.text,fontSize:e.font.row,fontFamily:N,paddingHorizontal:10,paddingVertical:8}});
@@ -0,0 +1 @@
1
+ "use strict";import{useCallback as k,useMemo as m,useRef as O,useState as V}from"react";import{ScrollView as W,StyleSheet as A,Text as l,TextInput as M,TouchableOpacity as T,View as c}from"react-native";import{Bookmark as N,ToolButton as z,Zap as $,useSafeAreaInsets as j,ToolEmptyState as E}from"@buoy-gg/shared-ui";import{useToolHeader as F}from"@buoy-gg/core";import{fastForwardIndex as _,fastForwardTo as K,macroToSteps as Z,replayAll as q,suggestMacroName as G}from"../record/condense";import{SCENARIO_TOOL as J}from"../catalog";import{scn as e}from"../ui/scnTheme";import{Footnote as Q,Notice as I,Panel as H,PanelSeparator as U,SectionLabel as B}from"../ui/primitives";import{jsx as n,jsxs as i}from"react/jsx-runtime";export function MacroReviewScreen({draft:t,onChange:a,onSave:g,onDiscard:h,busy:u}){const p=j({minBottom:16});const[f,L]=V(()=>G(t));const v=O(h);v.current=h;const R=k(()=>v.current(),[]);F({title:"Review recording",onBack:R,searchable:false},[]);const D=m(()=>t.filter(r=>r.enabled&&!r.noise),[t]);const y=m(()=>t.filter(r=>r.noise),[t]);const x=m(()=>_(t),[t]);const C=m(()=>{let r=0;return t.map(s=>s.precondition?0:++r)},[t]);const P=k((r,s)=>{a(t.map(w=>w.id===r?{...w,...s}:w))},[t,a]);const d=m(()=>Z(t),[t]);const b=m(()=>d.filter(r=>r.tool===J&&r.action==="waitFor").length,[d]);const S=D.length-(d.length-b);return i(W,{style:o.fill,contentContainerStyle:[o.content,{paddingBottom:p.bottom}],keyboardShouldPersistTaps:"handled",testID:"macro-review-scroll",children:[n(B,{icon:N,children:"Name"}),n(H,{children:n(M,{style:o.nameInput,value:f,onChangeText:L,placeholder:"Bag with a Charizard in it",placeholderTextColor:e.color.textTertiary,maxLength:80,returnKeyType:"done",testID:"macro-name-input"})}),n(B,{icon:$,count:D.length,children:"Steps"}),x>=0?n(c,{style:o.modeBar,children:n(T,{style:o.reset,onPress:()=>a(q(t)),activeOpacity:.7,accessibilityRole:"button",accessibilityLabel:"Perform every step instead of starting partway",testID:"macro-replay-all",children:n(l,{style:o.resetText,children:"\u21BA Perform every step"})})}):null,i(H,{children:[t.map((r,s)=>i(c,{children:[s>0?n(U,{}):null,n(X,{step:r,index:C[s],before:s<=x,onStartHere:r.lanes.includes("state")&&!r.precondition?()=>a(K(t,s)):void 0,onToggleEnabled:()=>P(r.id,{enabled:!r.enabled})}),s===x?i(c,{style:o.startLine,testID:"macro-start-line",children:[n(c,{style:o.startRule}),n(l,{style:o.startLabel,children:"the app starts here \xB7 the rest is performed"}),n(c,{style:o.startRule})]}):null]},r.id)),t.length===0?n(E,{testID:"macro-review-empty",otto:"shrug",title:"Nothing was recorded",message:"Taps on Buoy's own controls are ignored on purpose \u2014 drive the app itself and try again."}):null]}),S>0?n(I,{tone:"info",testID:"macro-review-collapsed",children:i(l,{style:o.noticeText,children:[S," shortcut step",S===1?"":"s"," collapsed \u2014 consecutive writes to the same store save once, at the value they ended on."]})}):null,b>0?n(I,{tone:"info",testID:"macro-review-waits",children:i(l,{style:o.noticeText,children:["Saves as ",d.length," steps: ",b," of them wait for the screen a tap opened, so the replay never runs ahead of the app."]})}):null,y.length?n(I,{tone:"sim",testID:"macro-review-unnameable",children:i(l,{style:o.noticeText,children:[y.length," tap",y.length===1?"":"s"," landed on something with no testID, label or text, so it can't be found again. Add a testID to replay ",y.length===1?"it":"them","."]})}):null,i(c,{style:o.actions,children:[n(z,{label:"Discard",variant:"ghost",onPress:h,disabled:u,testID:"macro-discard"}),n(z,{label:`Save ${d.length} step${d.length===1?"":"s"}`,variant:"primary",disabled:u||!d.length||!f.trim(),onPress:()=>g(f.trim(),t),testID:"macro-save"})]}),n(Q,{children:"Every step is performed the way you did it, which keeps working when the app's internals change. Tap \u201CStart here\u201D on the step you actually want to test and everything before it is written instantly instead \u2014 the setup stops costing you a minute a run, and it survives those earlier screens being redesigned."})]})}function X({step:t,index:a,before:g,onStartHere:h,onToggleEnabled:u}){const p=!t.enabled||t.noise;return i(c,{style:o.row,children:[i(T,{style:o.rowMain,onPress:u,disabled:t.noise,activeOpacity:.7,accessibilityRole:"checkbox",accessibilityState:{checked:!p},accessibilityLabel:t.title,testID:`macro-step-${a}`,children:[n(l,{style:[o.index,p&&o.dim],children:t.precondition?"\u21B1":a}),i(c,{style:o.rowText,children:[n(l,{style:[o.title,p&&o.dim,p&&o.struck],numberOfLines:2,children:t.title}),t.detail?n(l,{style:o.detail,numberOfLines:1,children:t.detail}):null]})]}),h&&!g?n(T,{style:o.startHere,onPress:h,activeOpacity:.7,accessibilityRole:"button",accessibilityLabel:`Start the macro here \u2014 everything up to \u201C${t.title}\u201D is set instantly`,testID:`macro-step-${a}-start-here`,children:n(l,{style:o.startHereText,children:"Start here"})}):n(l,{style:[o.laneStatic,g&&o.laneStaticInstant],testID:`macro-step-${a}-lane`,children:t.precondition?"first":t.noise?"can\u2019t replay":g?t.lane==="state"?"instant":"skipped":"performed"})]})}const o=A.create({fill:{flex:1,backgroundColor:"transparent"},content:{paddingBottom:32},nameInput:{color:e.color.text,fontSize:e.font.row,paddingHorizontal:e.textInset,paddingVertical:12},modeBar:{paddingHorizontal:e.gutter,paddingBottom:4},row:{flexDirection:"row",alignItems:"center",gap:8,paddingRight:e.textInset,minHeight:e.rowMinHeight},rowMain:{flex:1,minWidth:0,flexDirection:"row",alignItems:"center",gap:10,paddingLeft:e.textInset,paddingVertical:10},index:{color:e.color.textTertiary,fontSize:e.font.micro,fontWeight:"700",minWidth:14},rowText:{flex:1,minWidth:0},title:{color:e.color.text,fontSize:e.font.row,lineHeight:17},detail:{color:e.color.textTertiary,fontSize:e.font.micro,marginTop:2},dim:{color:e.color.textTertiary},struck:{textDecorationLine:"line-through"},startHere:{paddingHorizontal:8,paddingVertical:5,borderRadius:e.radius.pill,borderWidth:1,borderColor:e.color.border,backgroundColor:e.color.chip},startHereText:{color:e.color.textSecondary,fontSize:e.font.micro,fontWeight:"700"},laneStatic:{color:e.color.textTertiary,fontSize:e.font.micro},laneStaticInstant:{color:e.color.sim},startLine:{flexDirection:"row",alignItems:"center",gap:8,paddingHorizontal:e.textInset,paddingVertical:8,backgroundColor:e.color.simSoft},startRule:{flex:1,height:1,backgroundColor:e.color.simBorder},startLabel:{color:e.color.sim,fontSize:e.font.micro,fontWeight:"700",letterSpacing:.3},reset:{alignSelf:"flex-start",paddingVertical:4},resetText:{color:e.color.textSecondary,fontSize:e.font.footnote,fontWeight:"600"},noticeText:{color:e.color.textSecondary,fontSize:e.font.footnote,lineHeight:16},actions:{flexDirection:"row",justifyContent:"flex-end",gap:8,paddingHorizontal:e.gutter,paddingTop:12},empty:{color:e.color.textTertiary,fontSize:e.font.control,lineHeight:18,paddingHorizontal:e.textInset,paddingVertical:14}});
@@ -0,0 +1 @@
1
+ "use strict";import{Fragment as S,useCallback as W,useMemo as L,useState as O}from"react";import{ScrollView as P,StyleSheet as A,Switch as j,Text as i,TextInput as B,TouchableOpacity as _,View as c}from"react-native";import{Layers as N,Play as $,Hash as E,ToolButton as w}from"@buoy-gg/shared-ui";import{useToolHeader as M}from"@buoy-gg/core";import{describeStep as U,stepDurability as X}from"../catalog";import{coerceVarValue as Y,interpolate as q,interpolateString as G}from"../interpolate";import{scn as e,SCN_MONO as b}from"../ui/scnTheme";import{DurabilityChip as J,Footnote as K,Notice as x,Panel as I,PanelSeparator as C,SectionLabel as D}from"../ui/primitives";import{jsx as r,jsxs as l,Fragment as v}from"react/jsx-runtime";function Q(o){if(o==="durable")return"durable";if(o==="transient")return"transient";return"neutral"}const Z={durable:"survives restart",session:"until restart",transient:"until refetch \u26A0","one-shot":"one-shot"};function ee({variable:o,value:f,onChange:p}){const d=o.label??o.key;const u=`scenario-var-${o.key}`;if(o.type==="enum"&&o.options?.length){return l(c,{style:t.field,children:[r(i,{style:t.fieldLabel,children:d}),r(c,{style:t.segs,children:o.options.map(s=>{const y=String(f)===String(s);return r(_,{style:[t.seg,y?t.segOn:null],onPress:()=>p(s),accessibilityRole:"button",accessibilityState:{selected:y},testID:`${u}-${String(s)}`,children:r(i,{style:[t.segText,y?t.segTextOn:null],children:String(s)})},String(s))})})]})}if(o.type==="boolean"){return l(c,{style:[t.field,t.fieldRow],children:[r(i,{style:t.fieldLabel,children:d}),r(j,{value:f===true,onValueChange:s=>p(s),testID:u})]})}return l(c,{style:t.field,children:[r(i,{style:t.fieldLabel,children:d}),r(B,{style:t.input,value:f===void 0?"":String(f),onChangeText:s=>p(Y(o,s)),keyboardType:o.type==="number"?"numeric":"default",placeholder:o.type==="number"?"0":"value",placeholderTextColor:e.color.textTertiary,autoCapitalize:"none",autoCorrect:false,testID:u})]})}export function PreviewScreen({scenario:o,initialValues:f,check:p,currentBuild:d,mode:u,onRun:s,onAcceptDraft:y,onDiscardDraft:z,onBack:k}){const[h,V]=O(f);M({title:u==="review-draft"?"Review draft":"Before you run",subtitle:G(o.name,h),onBack:k,searchable:false},[o.id,u]);const a=L(()=>p(h),[p,h]);const F=W((n,g)=>{V(m=>({...m,[n]:g}))},[]);const H=!!o.authoredAgainst&&!!d&&o.authoredAgainst!==d&&o.verifiedAt?.build!==d;const R=!a.ok;return l(c,{style:t.fill,children:[l(P,{style:t.fill,contentContainerStyle:t.content,keyboardShouldPersistTaps:"handled",testID:"scenario-preview-scroll",children:[o.description?r(K,{testID:"scenario-preview-description",children:o.description}):null,o.vars.length?l(v,{children:[r(D,{icon:E,count:o.vars.length,children:"Variables"}),r(I,{children:o.vars.map((n,g)=>l(S,{children:[g>0?r(C,{}):null,r(ee,{variable:n,value:h[n.key],onChange:m=>F(n.key,m)})]},n.key))})]}):null,r(D,{icon:N,count:o.steps.length,children:"What this will do"}),r(I,{children:o.steps.map((n,g)=>{const m=q(n.params??{},h);const T=X(n);return l(S,{children:[g>0?r(C,{}):null,l(c,{style:t.step,children:[r(c,{style:t.stepIndex,children:r(i,{style:t.stepIndexText,children:g+1})}),l(c,{style:t.stepBody,children:[r(i,{style:t.stepLabel,children:U(n,m)}),l(i,{style:t.stepTool,children:[n.tool,".",n.action]})]}),r(J,{label:Z[T],tone:Q(T)})]})]},n.id)})}),H?r(x,{tone:"sim",testID:"scenario-preview-stale",children:l(i,{style:t.warnText,children:["Authored against app ",o.authoredAgainst," \u2014 unverified on this build. The response shape it overrides may have drifted."]})}):null,a.conflict?r(x,{tone:"sim",testID:"scenario-preview-conflict",children:l(i,{style:t.warnText,children:["Conflict: \u201C",a.conflict.activeName,"\u201D is active and touches the same ",a.conflict.overlaps.length===1?"target":"targets",". Running will swap \u2014 it deactivates first, so the two can never overlap silently."]})}):null,a.errors.length?r(x,{tone:"danger",testID:"scenario-preview-errors",children:a.errors.map(n=>l(i,{style:t.errorText,children:["\u2022 ",n]},n))}):null,a.warnings.length?r(x,{tone:"sim",children:a.warnings.map(n=>l(i,{style:t.warnText,children:["\u2022 ",n]},n))}):null,o.expectedOutcome?r(x,{tone:"info",testID:"scenario-preview-expected",children:l(i,{style:t.infoText,children:[r(i,{style:t.infoStrong,children:"Expected outcome: "}),o.expectedOutcome]})}):null]}),r(c,{style:t.footer,children:u==="review-draft"?l(v,{children:[r(w,{label:"Discard",variant:"ghost",onPress:()=>z?.(),style:t.grow,testID:"scenario-discard-draft"}),r(w,{label:"Accept to library",variant:"primary",onPress:()=>y?.(),style:t.grow,testID:"scenario-accept-draft"})]}):r(w,{label:a.conflict?"Swap & run":"Run",variant:"primary",onPress:()=>s(h),disabled:R,icon:r($,{size:12,color:e.color.accent}),style:t.grow,testID:"scenario-confirm-run"})})]})}const t=A.create({fill:{flex:1,backgroundColor:"transparent"},content:{paddingBottom:24},field:{paddingHorizontal:e.textInset,paddingVertical:10,gap:6},fieldRow:{flexDirection:"row",alignItems:"center",justifyContent:"space-between"},fieldLabel:{color:e.color.textTertiary,fontSize:e.font.micro,fontWeight:"600",letterSpacing:.6,textTransform:"uppercase"},segs:{flexDirection:"row",flexWrap:"wrap",gap:6},seg:{borderRadius:e.radius.row,borderWidth:1,borderColor:e.color.border,backgroundColor:e.color.chip,paddingHorizontal:12,paddingVertical:6},segOn:{backgroundColor:e.color.accentSoft,borderColor:e.color.accentBorderStrong},segText:{color:e.color.textSecondary,fontSize:e.font.control,fontFamily:b},segTextOn:{color:e.color.accent,fontWeight:"700"},input:{backgroundColor:e.color.row,borderRadius:e.radius.row,borderWidth:1,borderColor:e.color.border,color:e.color.text,fontSize:e.font.row,fontFamily:b,paddingHorizontal:10,paddingVertical:8},step:{flexDirection:"row",alignItems:"flex-start",gap:9,paddingHorizontal:e.textInset,paddingVertical:10},stepIndex:{width:18,height:18,borderRadius:e.radius.badge,backgroundColor:e.color.fill,alignItems:"center",justifyContent:"center",marginTop:1},stepIndexText:{color:e.color.textTertiary,fontSize:10,fontWeight:"700",fontFamily:b},stepBody:{flex:1,minWidth:0},stepLabel:{color:e.color.text,fontSize:e.font.control},stepTool:{color:e.color.textTertiary,fontSize:e.font.mono,fontFamily:b,marginTop:2},warnText:{color:e.color.sim,fontSize:e.font.control,lineHeight:17},errorText:{color:e.color.danger,fontSize:e.font.control,lineHeight:17},infoText:{color:"#7FA9FF",fontSize:e.font.control,lineHeight:17},infoStrong:{fontWeight:"700"},footer:{flexDirection:"row",gap:8,paddingHorizontal:e.gutter,paddingTop:10,paddingBottom:14,borderTopWidth:1,borderTopColor:e.color.border,backgroundColor:e.color.surface},grow:{flex:1}});
@@ -0,0 +1 @@
1
+ "use strict";import{useCallback as w,useState as a}from"react";import{Animated as d,PanResponder as O,Platform as P,StyleSheet as g,Text as i,TouchableOpacity as p,View as h}from"react-native";import{openBuoyTool as W,useSafeAreaInsets as z}from"@buoy-gg/shared-ui";import{recorderStore as v}from"../record/recorderStore";import{useActiveScenario as B,useRecording as j}from"../hooks/useScenarios";import{scn as o}from"../ui/scnTheme";import{jsx as r,jsxs as b,Fragment as k}from"react/jsx-runtime";export const BANNER_TOP_GAP=52;const _=54;export function RecordingBanner(){const t=j();const R=B();const x=z({minBottom:0});const[l]=a(()=>new d.ValueXY({x:0,y:0}));const[u,f]=a(false);const[m,S]=a(false);const C=w(async()=>{f(true);try{await v.stop()}finally{f(false)}},[]);const T=w(()=>{if(!W("scenarios"))S(true)},[]);const[D]=a(()=>O.create({onMoveShouldSetPanResponder:(c,I)=>Math.abs(I.dy)>6,onPanResponderMove:d.event([null,{dy:l.y}],{useNativeDriver:false}),onPanResponderRelease:()=>l.extractOffset()}));const n=t.status==="review";if(t.status!=="recording"&&!n)return null;const s=n?(t.draft??[]).filter(c=>c.enabled&&!c.noise).length:t.actions.length;const y=t.actions[t.actions.length-1];return r(d.View,{style:[e.wrap,{top:x.top+BANNER_TOP_GAP+(R?_:0)},{transform:l.getTranslateTransform()}],pointerEvents:"box-none",...D.panHandlers,children:b(h,{style:[e.banner,n&&e.bannerReview],testID:"recording-banner",accessible:false,children:[r(h,{style:[e.dot,n&&e.dotReview]}),b(h,{style:e.text,children:[r(i,{style:[e.title,n&&e.titleReview],children:n?`RECORDED \xB7 ${s} step${s===1?"":"s"}`:`\u25CF RECORDING \xB7 ${s} step${s===1?"":"s"}`}),r(i,{style:e.subtitle,numberOfLines:1,children:n?m?"Open Scenarios to review and save it.":"Review the steps, then save.":y?A(y.touch.target):"Drive the app \u2014 taps on Buoy itself are ignored."})]}),n?b(k,{children:[r(p,{style:e.discard,onPress:()=>void v.cancel(),accessibilityRole:"button",accessibilityLabel:"Discard the recording",testID:"recording-discard",children:r(i,{style:e.discardText,children:"Discard"})}),m?null:r(p,{style:e.review,onPress:T,accessibilityRole:"button",accessibilityLabel:"Review the recorded steps",testID:"recording-review",children:r(i,{style:e.reviewText,children:"Review"})})]}):r(p,{style:e.stop,onPress:C,disabled:u,accessibilityRole:"button",accessibilityLabel:"Stop recording and review the steps",testID:"recording-stop",children:r(i,{style:e.stopText,children:u?"\u2026":"Stop"})})]})})}function A(t){return t.label??t.text??t.testID??t.name}const e=g.create({wrap:{position:"absolute",left:12,right:12,zIndex:10001,alignItems:"center"},banner:{flexDirection:"row",alignItems:"center",gap:8,maxWidth:520,width:"100%",paddingLeft:14,paddingRight:8,paddingVertical:8,borderRadius:22,backgroundColor:"rgba(20, 20, 20, 0.94)",borderWidth:g.hairlineWidth,borderColor:o.color.dangerBorder,...P.select({ios:{shadowColor:"#000",shadowOpacity:.35,shadowRadius:12,shadowOffset:{width:0,height:6}},android:{elevation:8},default:{}})},dot:{width:8,height:8,borderRadius:4,backgroundColor:o.color.danger},text:{flex:1,minWidth:0},title:{color:o.color.danger,fontSize:10,fontWeight:"800",letterSpacing:.8},subtitle:{color:"rgba(255,255,255,0.85)",fontSize:11.5,marginTop:1},stop:{paddingHorizontal:12,height:28,borderRadius:14,alignItems:"center",justifyContent:"center",backgroundColor:"rgba(255, 59, 48, 0.15)"},stopText:{color:o.color.danger,fontSize:12,fontWeight:"700"},bannerReview:{borderColor:o.color.simBorder},dotReview:{backgroundColor:o.color.sim},titleReview:{color:o.color.sim},discard:{paddingHorizontal:12,height:28,borderRadius:14,alignItems:"center",justifyContent:"center",backgroundColor:"rgba(255,255,255,0.08)"},discardText:{color:"rgba(255,255,255,0.75)",fontSize:12,fontWeight:"600"},review:{paddingHorizontal:12,height:28,borderRadius:14,alignItems:"center",justifyContent:"center",backgroundColor:o.color.simSoft,borderWidth:g.hairlineWidth,borderColor:o.color.simBorder},reviewText:{color:o.color.sim,fontSize:12,fontWeight:"700"}});
@@ -0,0 +1 @@
1
+ "use strict";import{Fragment as z}from"react";import{ActivityIndicator as g,ScrollView as I,StyleSheet as S,Text as i,View as s}from"react-native";import{Check as w,Layers as D,RefreshCw as C,ToolButton as c,X as F}from"@buoy-gg/shared-ui";import{useToolHeader as P}from"@buoy-gg/core";import{scn as e,SCN_MONO as d}from"../ui/scnTheme";import{Notice as B,Panel as L,PanelSeparator as y,SectionLabel as R}from"../ui/primitives";import{jsx as o,jsxs as t,Fragment as v}from"react/jsx-runtime";export function RunProgressScreen({runState:n,busy:h,onUndoPartial:u,onKeepPartial:x,onRetry:b,onDone:T,onBack:k}){const p=n.report;const a=!!p&&!p.ok;P({title:a?"Run failed":n.done?"Run complete":"Running\u2026",subtitle:n.name,onBack:n.done?k:void 0,searchable:false},[n.done,a,n.name]);const m=p?.steps??n.steps;return t(s,{style:r.fill,children:[t(I,{style:r.fill,contentContainerStyle:r.content,testID:"scenario-run-scroll",children:[o(R,{icon:D,color:a?e.color.danger:e.color.accent,children:"Steps"}),t(L,{tone:a?"danger":"neutral",children:[m.length===0?t(s,{style:r.step,children:[o(g,{size:"small",color:e.color.accent}),o(i,{style:r.stepLabel,children:"Starting\u2026"})]}):null,m.map((l,f)=>t(z,{children:[f>0?o(y,{}):null,t(s,{style:r.step,children:[o(s,{style:r.mark,children:l.skipped?o(i,{style:r.skipped,children:"\u2013"}):l.ok?o(w,{size:13,color:e.color.accent}):o(F,{size:13,color:e.color.danger})}),t(s,{style:r.stepBody,children:[o(i,{style:[r.stepLabel,l.skipped?r.dim:null],testID:`scenario-step-${f}`,children:l.label}),l.error?o(i,{style:r.error,testID:`scenario-step-${f}-error`,children:l.error}):null,l.skipped?o(i,{style:r.dimSmall,children:"skipped"}):null]}),l.ms?t(i,{style:r.ms,children:[l.ms,"ms"]}):null]})]},l.id)),!n.done&&m.length?t(v,{children:[o(y,{}),t(s,{style:r.step,children:[o(g,{size:"small",color:e.color.accent}),o(i,{style:r.stepLabel,children:"Working\u2026"})]})]}):null]}),a?o(B,{tone:"danger",children:o(i,{style:r.errorText,children:"The run stopped at the failing step. Nothing after it ran \u2014 but the steps before it did, so choose what happens to them."})}):null]}),a?t(s,{style:r.footer,children:[o(c,{label:"Undo applied steps",variant:"primary",onPress:u,disabled:h,style:r.grow,testID:"scenario-undo-partial"}),o(c,{label:"Keep",variant:"ghost",onPress:x,testID:"scenario-keep-partial"}),o(c,{label:"Retry",variant:"ghost",onPress:b,disabled:h,icon:o(C,{size:12,color:e.color.textSecondary}),testID:"scenario-retry"})]}):n.done?o(s,{style:r.footer,children:o(c,{label:"Done",variant:"primary",onPress:T,style:r.grow,testID:"scenario-run-done"})}):null]})}const r=S.create({fill:{flex:1,backgroundColor:"transparent"},content:{paddingBottom:24},step:{flexDirection:"row",alignItems:"flex-start",gap:9,paddingHorizontal:e.textInset,paddingVertical:10},mark:{width:18,alignItems:"center",marginTop:1},skipped:{color:e.color.textTertiary,fontSize:13,fontFamily:d},stepBody:{flex:1,minWidth:0},stepLabel:{color:e.color.text,fontSize:e.font.control},dim:{color:e.color.textTertiary},dimSmall:{color:e.color.textTertiary,fontSize:e.font.mono,marginTop:2,fontFamily:d},error:{color:e.color.danger,fontSize:e.font.mono,marginTop:3,fontFamily:d},ms:{color:e.color.textTertiary,fontSize:e.font.micro,fontFamily:d,marginTop:3},errorText:{color:e.color.danger,fontSize:e.font.control,lineHeight:17},footer:{flexDirection:"row",gap:8,paddingHorizontal:e.gutter,paddingTop:10,paddingBottom:14,borderTopWidth:1,borderTopColor:e.color.border,backgroundColor:e.color.surface},grow:{flex:1}});
@@ -0,0 +1 @@
1
+ "use strict";import{useCallback as P,useEffect as I,useState as i}from"react";import{Animated as f,PanResponder as C,Platform as H,StyleSheet as p,Text as s,TouchableOpacity as m,View as u}from"react-native";import{useSafeAreaInsets as D}from"@buoy-gg/shared-ui";import{useActiveScenario as _}from"../hooks/useScenarios";import{BANNER_TOP_GAP as T,RecordingBanner as A}from"./RecordingBanner";import{scenariosStore as L}from"../store/scenariosStore";import{scn as n}from"../ui/scnTheme";import{jsx as t,jsxs as h,Fragment as O}from"react/jsx-runtime";function W(o){if(!o)return null;const r=o-Date.now();if(r<=0)return"expiring";const a=Math.floor(r/36e5);const l=Math.round(r%36e5/6e4);return a?`auto-off ${a}h`:`auto-off ${l}m`}export function ScenarioBanner(){const o=_();const r=D({minBottom:0});const[a,l]=i(false);const[c]=i(()=>new f.ValueXY({x:0,y:0}));const g=P(()=>{void L.deactivate()},[]);const[R,b]=i(false);const[d,y]=i(false);I(()=>{if(!d)return;const w=setTimeout(()=>y(false),2200);return()=>clearTimeout(w)},[d]);const[x]=i(()=>C.create({onMoveShouldSetPanResponder:(w,v)=>Math.abs(v.dy)>6,onPanResponderMove:f.event([null,{dy:c.y}],{useNativeDriver:false}),onPanResponderRelease:()=>{c.extractOffset()}}));if(!o)return null;const S=W(o.expiresAt);if(a){return t(f.View,{style:[e.wrap,{top:r.top+T},{transform:c.getTranslateTransform()}],pointerEvents:"box-none",...x.panHandlers,children:h(m,{style:e.pill,onPress:()=>l(false),accessibilityRole:"button",accessibilityLabel:"Simulation active. Expand for details.",testID:"scenario-banner-pill",children:[t(u,{style:e.dot}),t(s,{style:e.pillText,children:"SIMULATED"})]})})}return t(f.View,{style:[e.wrap,{top:r.top+T},{transform:c.getTranslateTransform()}],pointerEvents:"box-none",...x.panHandlers,children:h(u,{style:e.banner,testID:"scenario-banner",accessible:false,children:[t(u,{style:e.dot}),h(m,{style:e.text,onPress:()=>l(true),accessibilityRole:"button",accessibilityLabel:`Simulated: ${o.name}. ${o.effects.length} changes.`,testID:"scenario-banner-body",children:[t(s,{style:e.title,children:"\u25B2 SIMULATED"}),t(s,{style:[e.subtitle,d&&e.hint],numberOfLines:1,children:d?"Hold \u23FB to stop the scenario":`${o.name} \xB7 ${o.effects.length} ${o.effects.length===1?"change":"changes"}`})]}),S?t(u,{style:e.expiry,children:t(s,{style:e.expiryText,children:S})}):null,t(m,{style:[e.off,R&&e.offHolding],onPress:()=>y(true),onLongPress:g,delayLongPress:600,onPressIn:()=>b(true),onPressOut:()=>b(false),accessibilityActions:[{name:"activate"},{name:"longpress"}],onAccessibilityAction:g,accessibilityHint:"Hold to stop",accessibilityRole:"button",accessibilityLabel:"Deactivate the scenario",testID:"scenario-stop-simulation",children:t(s,{style:e.offText,children:"\u23FB"})})]})})}const e=p.create({wrap:{position:"absolute",left:12,right:12,zIndex:9998,alignItems:"center"},banner:{flexDirection:"row",alignItems:"center",gap:8,maxWidth:520,width:"100%",paddingLeft:14,paddingRight:8,paddingVertical:8,borderRadius:22,backgroundColor:"rgba(20, 20, 20, 0.94)",borderWidth:p.hairlineWidth,borderColor:n.color.simBorder,...H.select({ios:{shadowColor:"#000",shadowOpacity:.35,shadowRadius:12,shadowOffset:{width:0,height:6}},android:{elevation:8},default:{}})},pill:{flexDirection:"row",alignItems:"center",gap:6,paddingHorizontal:12,paddingVertical:6,borderRadius:22,backgroundColor:"rgba(20, 20, 20, 0.94)",borderWidth:p.hairlineWidth,borderColor:n.color.simBorder},pillText:{color:n.color.sim,fontSize:10,fontWeight:"800",letterSpacing:.8},dot:{width:7,height:7,borderRadius:2,backgroundColor:n.color.sim},text:{flex:1,minWidth:0},title:{color:n.color.sim,fontSize:10,fontWeight:"800",letterSpacing:.8},subtitle:{color:"rgba(255,255,255,0.85)",fontSize:11.5,marginTop:1},expiry:{paddingHorizontal:6,paddingVertical:2,borderRadius:6,backgroundColor:n.color.simSoft},expiryText:{color:n.color.sim,fontSize:10,fontWeight:"600"},off:{width:28,height:28,borderRadius:14,alignItems:"center",justifyContent:"center",backgroundColor:"rgba(255, 131, 41, 0.15)"},offHolding:{backgroundColor:n.color.sim},offText:{color:n.color.sim,fontSize:13},hint:{color:n.color.sim}});export function ScenariosOverlay(){return h(O,{children:[t(ScenarioBanner,{}),t(A,{})]})}
@@ -0,0 +1 @@
1
+ "use strict";import{Fragment as z}from"react";import{ScrollView as C,StyleSheet as V,Text as c,TouchableOpacity as F,View as s}from"react-native";import{CheckCircle as A,ChevronRight as L,Copy as O,FileText as j,Layers as H,Play as P,ToolButton as y,Trash as R,copyToClipboard as B}from"@buoy-gg/shared-ui";import{useToolHeader as N}from"@buoy-gg/core";import{describeStep as _,isAdvancedStep as k,stepDurability as M}from"../catalog";import{normalizeFolder as E}from"../folders";import{scn as l,SCN_MONO as m}from"../ui/scnTheme";import{DurabilityChip as $,Footnote as D,Panel as b,PanelSeparator as i,SectionLabel as x}from"../ui/primitives";import{displayName as W}from"./ScenarioRow";import{jsx as e,jsxs as r,Fragment as u}from"react/jsx-runtime";const q={durable:"survives restart",session:"until restart",transient:"until refetch \u26A0","one-shot":"one-shot"};function J(t){if(t==="durable")return"durable";if(t==="transient")return"transient";return"neutral"}export function ScenarioDetailScreen({scenario:t,onRun:h,onDelete:g,onDuplicate:a,onExported:p,onMoveToFolder:f,onBack:w}){N({title:W(t.name),subtitle:t.source==="code"?"shipped with this app":"saved on this device",onBack:w,searchable:false},[t.id]);const v=t.source==="device";const S=E(t.folder);return r(C,{style:o.fill,contentContainerStyle:o.content,testID:"scenario-detail-scroll",children:[t.description?e(D,{children:t.description}):null,e(x,{icon:H,count:t.steps.length,children:"Steps"}),e(b,{children:t.steps.map((d,T)=>{const I=M(d);return r(z,{children:[T>0?e(i,{}):null,r(s,{style:o.step,children:[e(s,{style:o.stepIndex,children:e(c,{style:o.stepIndexText,children:T+1})}),r(s,{style:o.stepBody,children:[e(c,{style:o.stepLabel,children:_(d)}),r(c,{style:o.stepTool,children:[d.tool,".",d.action,k(d)?" \xB7 advanced":""]})]}),e($,{label:q[I],tone:J(I)})]})]},d.id)})}),t.expectedOutcome?r(u,{children:[e(x,{icon:A,children:"Expected outcome"}),e(b,{children:e(c,{style:o.expected,children:t.expectedOutcome})})]}):null,e(x,{icon:j,children:"Details"}),r(b,{children:[e(n,{label:"Folder",value:S??"No folder",muted:!S,onPress:v?f:void 0,testID:"scenario-detail-folder"}),e(i,{}),e(n,{label:"Source",value:t.source==="code"?"shipped with this app":"saved on this device"}),e(i,{}),e(n,{label:"Version",value:`v${t.version}`}),t.author?r(u,{children:[e(i,{}),e(n,{label:"Author",value:t.author})]}):null,t.authoredAgainst?r(u,{children:[e(i,{}),e(n,{label:"Authored against",value:t.authoredAgainst})]}):null,e(i,{}),e(n,{label:"Runs",value:String(t.usage?.runs??0)}),t.tags?.length?r(u,{children:[e(i,{}),e(n,{label:"Tags",value:t.tags.join(", ")})]}):null]}),r(s,{style:o.footer,children:[e(y,{label:"Run",variant:"primary",onPress:h,icon:e(P,{size:12,color:l.color.accent}),style:o.grow,testID:"scenario-detail-run"}),e(y,{label:"Export",variant:"ghost",onPress:()=>{void B(JSON.stringify(t,null,2));p?.()},icon:e(O,{size:12,color:l.color.textSecondary}),style:o.grow,testID:"scenario-detail-export"})]}),e(s,{style:o.footer,children:v?e(y,{label:"Delete",variant:"danger",onPress:g,icon:e(R,{size:12,color:l.color.danger}),style:o.grow,testID:"scenario-detail-delete"}):e(y,{label:"Make an editable copy",variant:"ghost",onPress:a,style:o.grow,testID:"scenario-detail-duplicate"})}),t.source==="code"?e(D,{children:"Shipped scenarios are read-only on the device \u2014 they change in a pull request, with the code they bend."}):null]})}function n({label:t,value:h,muted:g,onPress:a,testID:p}){const f=r(u,{children:[e(c,{style:o.detailLabel,children:t}),e(c,{style:[o.detailValue,g&&o.detailValueMuted,a&&o.detailValueAction],numberOfLines:2,children:h}),a?e(L,{size:13,color:l.color.textTertiary}):null]});if(!a){return e(s,{style:o.detail,testID:p,children:f})}return e(F,{style:o.detail,onPress:a,activeOpacity:.7,accessibilityRole:"button",accessibilityLabel:`${t}: ${h}. Change it.`,testID:p,children:f})}const o=V.create({fill:{flex:1,backgroundColor:"transparent"},content:{paddingBottom:32},step:{flexDirection:"row",alignItems:"flex-start",gap:9,paddingHorizontal:l.textInset,paddingVertical:10},stepIndex:{width:18,height:18,borderRadius:l.radius.badge,backgroundColor:l.color.fill,alignItems:"center",justifyContent:"center",marginTop:1},stepIndexText:{color:l.color.textTertiary,fontSize:10,fontWeight:"700",fontFamily:m},stepBody:{flex:1,minWidth:0},stepLabel:{color:l.color.text,fontSize:l.font.control},stepTool:{color:l.color.textTertiary,fontSize:l.font.mono,fontFamily:m,marginTop:2},expected:{color:l.color.textSecondary,fontSize:l.font.control,lineHeight:18,paddingHorizontal:l.textInset,paddingVertical:12},detail:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",gap:8,minHeight:40,paddingHorizontal:l.textInset,paddingVertical:10},detailLabel:{color:l.color.textTertiary,fontSize:l.font.control},detailValue:{flex:1,textAlign:"right",color:l.color.textSecondary,fontSize:l.font.control,fontFamily:m},detailValueMuted:{color:l.color.textTertiary},detailValueAction:{color:l.color.accent},footer:{flexDirection:"row",gap:8,paddingHorizontal:l.gutter,paddingTop:12},grow:{flex:1}});
@@ -0,0 +1 @@
1
+ "use strict";import{ScrollView as g,StyleSheet as p,Text as u,TouchableOpacity as m,View as y}from"react-native";import{countInFolder as f,inFolder as h,UNGROUPED as S}from"../folders";import{scn as o,SCN_MONO as x}from"../ui/scnTheme";import{jsx as i,jsxs as w}from"react/jsx-runtime";export function folderMatches(r,l){if(l===null)return true;return h(r,l)}function a(r){if(r===null)return"__all__";if(r===void 0)return"__ungrouped__";return r}export function ScenarioFolderBar({folders:r,active:l,onChange:b,scenarios:c}){const s=f(c,void 0);const d=[{filter:null,label:"All",count:c.length},...r.map(e=>({filter:e,label:e,count:f(c,e)}))];if(s){d.push({filter:void 0,label:S,count:s})}return i(g,{horizontal:true,showsHorizontalScrollIndicator:false,style:t.scroll,contentContainerStyle:t.row,testID:"scenarios-folder-bar",children:d.map(e=>{const n=l===e.filter||typeof l==="string"&&typeof e.filter==="string"&&l.toLowerCase()===e.filter.toLowerCase();return w(m,{testID:`scenarios-folder-${a(e.filter)}`,accessibilityRole:"button",accessibilityLabel:`Show ${e.label} scenarios`,accessibilityState:{selected:n},activeOpacity:.8,onPress:()=>b(e.filter),style:[t.chip,n&&t.chipActive],children:[i(u,{style:[t.label,n&&t.labelActive],numberOfLines:1,children:e.label}),i(y,{style:[t.countPill,n&&t.countPillActive],children:i(u,{style:[t.count,n&&t.countActive],testID:`scenarios-folder-${a(e.filter)}-count`,children:e.count})})]},a(e.filter))})})}const t=p.create({scroll:{flexGrow:0},row:{flexDirection:"row",gap:8,paddingHorizontal:o.gutter,paddingTop:10,paddingBottom:2},chip:{flexDirection:"row",alignItems:"center",gap:6,backgroundColor:o.color.chip,borderRadius:o.radius.card,borderWidth:1,borderColor:o.color.border,paddingVertical:6,paddingHorizontal:10,minHeight:32,maxWidth:200},chipActive:{backgroundColor:o.color.accentSoft,borderColor:o.color.accentBorderStrong},label:{fontSize:o.font.footnote,color:o.color.textSecondary,fontWeight:"500",flexShrink:1},labelActive:{color:o.color.accent,fontWeight:"600"},countPill:{minWidth:18,alignItems:"center",borderRadius:o.radius.badge,backgroundColor:o.color.fill,paddingHorizontal:4,paddingVertical:1},countPillActive:{backgroundColor:o.color.accentSoft},count:{fontSize:o.font.micro,fontWeight:"600",fontFamily:x,color:o.color.textTertiary},countActive:{color:o.color.accent}});
@@ -0,0 +1 @@
1
+ "use strict";import{memo as $}from"react";import{StyleSheet as y,TouchableOpacity as g,View as D}from"react-native";import{CompactRow as S}from"@buoy-gg/shared-ui";import{normalizeFolder as v}from"../folders";import{scn as r}from"../ui/scnTheme";import{DurabilityChip as c,PillLabel as w}from"../ui/primitives";import{jsx as l,Fragment as x,jsxs as I}from"react/jsx-runtime";export function displayName(t){return t.replace(/\{\{\s*(\w+)\s*\}\}/g,"\u27E8$1\u27E9")}function u(t){const e=[`v${t.version}`];if(t.author)e.push(t.author);const s=t.usage?.runs??0;if(s)e.push(`${s}\xD7`);if(t.unsavedToRepo)e.push("unsaved to repo");if(t.source==="draft")e.push("inert until you accept");return e.join(" \xB7 ")}function R(t){if(t.source==="code")return{label:"Shipped",color:r.color.accent};if(t.source==="draft")return{label:"Draft",color:r.color.sim};return{label:"Saved",color:r.color.info}}function T({scenario:t,currentBuild:e,disabled:s,showFolder:p,onOpen:m,onRun:b,testID:d}){const o=d??`scenario-row-${t.id}`;const a=R(t);const i=v(t.folder);const f=t.source==="draft";const n=f?{label:"Review",color:r.color.sim}:{label:"Run",color:r.color.accent};const h=!!t.authoredAgainst&&!!e&&t.authoredAgainst!==e&&t.verifiedAt?.build!==e;return l(S,{statusDotColor:a.color,statusLabel:a.label,statusSublabel:`${t.steps.length} step${t.steps.length===1?"":"s"}`,primaryText:displayName(t.name),primaryTestID:`${o}-name`,secondaryText:u(t),secondaryTestID:`${o}-meta`,secondaryAccessory:I(x,{children:[p&&i?l(c,{label:i,tone:"neutral",testID:`${o}-folder`}):null,h?l(c,{label:`stale \xB7 ${t.authoredAgainst}`,tone:"transient",testID:`${o}-stale`}):null]}),trailingAction:l(g,{onPress:b,disabled:s,style:s?L.disabled:null,hitSlop:A,accessibilityRole:"button",accessibilityLabel:`${n.label} ${displayName(t.name)}`,testID:`${o}-run`,children:l(D,{children:l(w,{label:n.label,color:n.color})})}),showChevron:true,onPress:m,testID:o,accessibilityLabel:`${displayName(t.name)}. ${a.label}, ${t.steps.length} step${t.steps.length===1?"":"s"}, ${u(t)}. Open details.`})}export const ScenarioRow=$(T);const A={top:8,bottom:8,left:8,right:8};const L=y.create({disabled:{opacity:.4}});
@@ -0,0 +1 @@
1
+ "use strict";import{useCallback as S,useMemo as U,useState as H}from"react";import{Alert as W,ScrollView as Z,StyleSheet as G,Text as F,TouchableOpacity as X,View as N}from"react-native";import{Box as ee,FileText as oe,Smartphone as te,ToolbarCopyButton as re,Zap as ne,useLimit as ie,useSafeAreaInsets as ae}from"@buoy-gg/shared-ui";import{useToolHeader as se,useToolHost as ce}from"@buoy-gg/core";import{resolveVarValues as de}from"../interpolate";import{scenariosStore as r}from"../store/scenariosStore";import{useRecording as le,useScenariosRunState as ue,useScenariosState as me}from"../hooks/useScenarios";import{recorderStore as I}from"../record/recorderStore";import{macroToSteps as he}from"../record/condense";import{MacroReviewScreen as fe}from"./MacroReviewScreen";import{scn as c}from"../ui/scnTheme";import{Footnote as C,Panel as _,SectionLabel as A}from"../ui/primitives";import{ActiveCard as pe}from"./ActiveCard";import{DeactivateReportCard as ve}from"./DeactivateReportCard";import{PreviewScreen as ye}from"./PreviewScreen";import{RunProgressScreen as ge}from"./RunProgressScreen";import{ScenarioDetailScreen as we}from"./ScenarioDetailScreen";import{ScenarioRow as j,displayName as Se}from"./ScenarioRow";import{ScenarioFolderBar as be,folderMatches as De}from"./ScenarioFolderBar";import{FolderPickerScreen as Re}from"./FolderPickerScreen";import{jsx as o,jsxs as b,Fragment as J}from"react/jsx-runtime";function Te(t){const f=t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,40);return`macro-${f||"recording"}-${Date.now().toString(36)}`}export function ScenariosBody({currentBuild:t}){const f=me();const y=ue();const u=le();const n=ce();const D=ae({minBottom:16});const k=ie("scenarios.saved");const[m,l]=H({name:"list"});const[x,p]=H(false);const d=S(()=>l({name:"list"}),[]);const L=S(e=>i=>{return r.checkBeforeRun(e,i)},[]);const R=S(async(e,i)=>{p(true);l({name:"run"});try{const a=await r.run(e,i);if(a.ok){n.showToast(`Scenario active \u2014 ${a.effects.length} change${a.effects.length===1?"":"s"}`,"success");r.clearRunState();l({name:"list"});n.minimize()}}catch(a){n.showToast(a instanceof Error?a.message:String(a),"error");l({name:"list"})}finally{p(false)}},[n]);const w=S(async()=>{p(true);try{await r.deactivate()}finally{p(false)}},[]);const P=S(async()=>{await I.start();const{status:e,reason:i}=I.getState();if(e!=="recording"){n.showToast(i??"Recording isn't available in this app.","error");return}n.minimize()},[n]);const v=S(async(e,i)=>{p(true);try{const a=he(i);const T=Date.now();await r.saveScenario({id:Te(e),name:e,version:1,source:"device",author:"recorded here",vars:[],steps:a,authoredAgainst:t,unsavedToRepo:true,createdAt:T,updatedAt:T,usage:{runs:0}});await I.cancel();n.showToast(`Saved \u201C${e}\u201D`,"success");d()}catch(a){n.showToast(a instanceof Error?a.message:String(a),"error")}finally{p(false)}},[t,d,n]);if(u.status==="review"&&u.draft){return o(fe,{draft:u.draft,busy:x,onChange:e=>I.setDraft(e),onSave:v,onDiscard:()=>{W.alert("Discard this recording?","The steps you just recorded will be thrown away.",[{text:"Keep",style:"cancel"},{text:"Discard",style:"destructive",onPress:()=>{void I.cancel();d()}}])}})}if(m.name==="run"&&y){return o(ge,{runState:y,busy:x,onUndoPartial:async()=>{p(true);try{await r.undoPartialRun();n.showToast("Applied steps undone","success")}finally{p(false);d()}},onKeepPartial:()=>{r.clearRunState();d()},onRetry:()=>{const e=y.scenarioId;const i=r.find(e);if(i){void R(e,r.lastValuesFor(e))}},onDone:()=>{r.clearRunState();d()},onBack:()=>{r.clearRunState();d()}})}if(m.name==="preview"){const e=r.find(m.scenarioId);if(!e)return o(B,{...g()});const i={...de(e.vars),...r.lastValuesFor(e.id)};return o(ye,{scenario:e,initialValues:i,check:L(e),currentBuild:t,mode:m.mode,onRun:a=>{r.rememberVarValues(e.id,a);void R(e.id,a)},onAcceptDraft:async()=>{await r.acceptDraft(e.id);n.showToast("Accepted \u2014 it's now in your library","success");d()},onDiscardDraft:async()=>{await r.discardDraft(e.id);n.showToast("Draft discarded","info");d()},onBack:d})}if(m.name==="detail"){const e=r.find(m.scenarioId);if(!e)return o(B,{...g()});return o(we,{scenario:e,onRun:()=>l({name:"preview",scenarioId:e.id,mode:"run"}),onDelete:()=>{W.alert("Delete scenario?",`\u201C${Se(e.name)}\u201D will be removed from this device.`,[{text:"Cancel",style:"cancel"},{text:"Delete",style:"destructive",onPress:()=>{void r.deleteScenario(e.id);d()}}])},onDuplicate:async()=>{const i={...e,id:`${e.id}-copy-${Date.now().toString(36)}`,name:`${e.name} (copy)`,source:"device",version:1,unsavedToRepo:true};await r.saveScenario(i);n.showToast("Copied to this device","success");d()},onExported:()=>n.showToast("Scenario JSON copied","success"),onMoveToFolder:()=>l({name:"folder",scenarioId:e.id}),onBack:d})}if(m.name==="folder"){const e=r.find(m.scenarioId);if(!e)return o(B,{...g()});return o(Re,{scenario:e,folders:r.folders(),onPick:async i=>{try{await r.setFolder(e.id,i);n.showToast(i?`Filed under ${i}`:"Removed from its folder","success")}catch(a){n.showToast(a instanceof Error?a.message:String(a),"error")}l({name:"detail",scenarioId:e.id})},onBack:()=>l({name:"detail",scenarioId:e.id})})}function g(){return{state:f,insets:D,host:n,busy:x,savedLimit:k,currentBuild:t,onOpen:e=>l({name:"detail",scenarioId:e}),onRun:e=>l({name:"preview",scenarioId:e,mode:"run"}),onReviewDraft:e=>l({name:"preview",scenarioId:e,mode:"review-draft"}),onDeactivate:w,onRerun:()=>{const e=f.active;if(e)void R(e.scenarioId,e.varValues)},onStartRecording:P,canRecord:I.canRecord(),recordUnavailable:u.status==="unavailable"?u.reason:null}}return o(B,{...g()})}function K(t,f,y){if(t)return"No matches.";if(f===void 0)return"Nothing unfiled here.";if(f!==null)return`Nothing in ${f}.`;return y}function B({state:t,insets:f,host:y,busy:u,savedLimit:n,currentBuild:D,onOpen:k,onRun:m,onReviewDraft:l,onDeactivate:x,onRerun:p,onStartRecording:d,canRecord:L,recordUnavailable:R}){const[w,P]=H(null);const v=y.search.trim().toLowerCase();const g=s=>!v||s.name.toLowerCase().includes(v)||(s.folder??"").toLowerCase().includes(v)||(s.tags??[]).some(Q=>Q.toLowerCase().includes(v))||(s.author??"").toLowerCase().includes(v);const e={code:t.code.filter(g),device:t.device.filter(g),drafts:t.drafts.filter(g)};const i=r.folders();const a=[...e.code,...e.device,...e.drafts];const T=s=>De(s,w);const $=e.code.filter(T);const V=e.device.filter(T);const z=e.drafts.filter(T);const Y=t.device.length>=n;const q=r.isLongRunning();const O=w===null&&i.length>0;const M=S(()=>({shipped:t.code,saved:t.device,drafts:t.drafts,active:t.active??null}),[t.code,t.device,t.drafts,t.active]);const E=U(()=>o(re,{value:M,buttonStyle:h.headerButton,testID:"scenarios-copy-library",accessibilityLabel:"Copy the scenario library as JSON"}),[M]);se({actions:E},[E]);return b(N,{style:h.fill,children:[i.length?o(be,{folders:i,active:w,onChange:P,scenarios:a}):null,b(Z,{style:h.fill,contentContainerStyle:[h.content,{paddingBottom:f.bottom}],keyboardShouldPersistTaps:"handled",testID:"scenarios-scroll",children:[L?b(X,{style:h.record,onPress:d,disabled:u,activeOpacity:.75,accessibilityRole:"button",accessibilityLabel:"Record what you do next as a macro",testID:"scenarios-record",children:[o(N,{style:h.recordDot}),b(N,{style:h.recordText,children:[o(F,{style:h.recordTitle,children:"Record a macro"}),o(F,{style:h.recordHint,children:"Do the flow once. Buoy writes down the steps."})]})]}):null,R?o(C,{tone:"warning",testID:"scenarios-record-unavailable",children:R}):null,t.lastDeactivateReport?o(ve,{report:t.lastDeactivateReport,onDismiss:()=>r.dismissDeactivateReport()}):null,t.active?b(J,{children:[o(A,{icon:ne,color:c.color.sim,testID:"scenarios-active-label",children:"Active now"}),o(pe,{active:t.active,build:D,busy:u,onDeactivate:x,onRerun:p,onCopied:()=>y.showToast("Repro info copied \u2014 paste into the ticket","success")}),q?o(C,{tone:"warning",testID:"scenarios-long-running",children:"This scenario has been active for a while. If you have moved on to something else, deactivate it before you test anything real."}):null]}):null,z.length?b(J,{children:[o(A,{icon:oe,color:c.color.sim,count:z.length,testID:"scenarios-drafts-label",children:"Awaiting review"}),z.map(s=>o(j,{scenario:s,currentBuild:D,disabled:u,showFolder:O,onOpen:()=>l(s.id),onRun:()=>l(s.id),testID:`scenario-draft-${s.id}`},s.id)),o(C,{children:"A scenario sent from chat or the desktop never runs on its own. You review what it will do, then accept it."})]}):null,o(A,{icon:ee,count:$.length,testID:"scenarios-code-label",children:"Shipped with this app"}),$.length?$.map(s=>o(j,{scenario:s,currentBuild:D,disabled:u,showFolder:O,onOpen:()=>k(s.id),onRun:()=>m(s.id)},s.id)):o(_,{children:o(F,{style:h.empty,testID:"scenarios-code-empty",children:K(v,w,"None yet \u2014 your developers can ship a QA menu with the app using defineScenario().")})}),o(A,{icon:te,color:c.color.info,count:V.length,testID:"scenarios-device-label",children:"Saved on this device"}),V.length?V.map(s=>o(j,{scenario:s,currentBuild:D,disabled:u,showFolder:O,onOpen:()=>k(s.id),onRun:()=>m(s.id)},s.id)):o(_,{children:o(F,{style:h.empty,testID:"scenarios-device-empty",children:K(v,w,"Nothing saved here yet. Ask your AI assistant to save the state you just set up as a scenario.")})}),Y?b(C,{tone:"warning",testID:"scenarios-limit",children:["Your plan keeps ",n," saved scenario",n===1?"":"s"," on a device. Scenarios shipped in code don't count toward it."]}):null,o(C,{children:i.length?"Scenarios only run in development builds. Tap a row to see exactly what it will do before you run it.":"Scenarios only run in development builds. Open one and tap its Folder row to group it by flow \u2014 Checkout, Login \u2014 and a folder bar appears up here."})]})]})}const h=G.create({fill:{flex:1,backgroundColor:"transparent"},content:{paddingBottom:24},record:{flexDirection:"row",alignItems:"center",gap:10,marginHorizontal:c.gutter,marginTop:8,marginBottom:4,paddingHorizontal:c.textInset,paddingVertical:12,borderRadius:c.radius.card,borderWidth:1,borderColor:c.color.simBorder,backgroundColor:c.color.simSoft},recordDot:{width:10,height:10,borderRadius:5,backgroundColor:c.color.sim},recordText:{flex:1,minWidth:0},recordTitle:{color:c.color.sim,fontSize:c.font.row,fontWeight:"700"},recordHint:{color:c.color.textSecondary,fontSize:c.font.micro,marginTop:2},headerButton:{width:32,height:32,borderRadius:8,backgroundColor:c.color.chip,borderWidth:1,borderColor:c.color.border,alignItems:"center",justifyContent:"center"},empty:{color:c.color.textTertiary,fontSize:c.font.control,lineHeight:18,paddingHorizontal:c.textInset,paddingVertical:14}});
@@ -0,0 +1 @@
1
+ "use strict";import{ScenariosBody as o}from"./ScenariosBody";import{jsx as i}from"react/jsx-runtime";export function ScenariosToolView({currentBuild:r}){return i(o,{currentBuild:r})}
@@ -0,0 +1 @@
1
+ "use strict";import{normalizeFolder as u}from"./folders";export function withStepIds(e=[],r){return e.map((s,t)=>({...s,id:s.id??`${r}${t+1}`}))}export function defineScenario(e){const r=Date.now();return{id:e.id,name:e.name,description:e.description,version:e.version??1,source:"code",author:e.author,vars:e.vars??[],steps:withStepIds(e.steps,"s"),undoSteps:e.undoSteps?withStepIds(e.undoSteps,"u"):void 0,tags:e.tags,folder:u(e.folder),expectedOutcome:e.expectedOutcome,authoredAgainst:e.authoredAgainst,createdAt:r,updatedAt:r,usage:{runs:0}}}
@@ -0,0 +1 @@
1
+ "use strict";import{catalogEntry as k,conflictKeysOverlap as T,describeStep as $,SCENARIO_TOOL as y,stepConflictKeys as I}from"./catalog";import{collectPlaceholders as S,interpolate as v,interpolateString as A}from"./interpolate";export const STEP_TIMEOUT_MS=1e4;export const SCENARIO_TIMEOUT_MS=6e4;export const WAIT_FOR_TIMEOUT_MS=6e3;const O=150;const _=new Set(["scenarios"]);function L(e){return e.action==="reloadApp"||e.action==="reload"}export function preflight(e,n,i){const t=[];const c=[];if(e.source==="draft"){t.push("This scenario is still a draft \u2014 review and accept it before running.")}if(!e.steps.length)t.push("This scenario has no steps.");const s=new Set(e.vars.map(r=>r.key));const l=new Set;for(const r of e.steps)S(r.params,l);S(e.name,l);for(const r of l){if(!s.has(r)){t.push(`{{${r}}} is used but never declared as a variable.`)}else if(!(r in n)){t.push(`Variable "${r}" needs a value.`)}}for(const r of s){if(!l.has(r))c.push(`Variable "${r}" is never used.`)}const f=new Set(i.availableTools);e.steps.forEach((r,m)=>{const d=`Step ${m+1}`;if(_.has(r.tool)){t.push(`${d}: a scenario cannot run another scenario.`);return}if(r.tool===y)return;if(!f.has(r.tool)){t.push(`${d}: "${r.tool}" is not installed in this app, so this step cannot run.`);return}const p=i.availableActions?.[r.tool];if(p&&!p.includes(r.action)){t.push(`${d}: "${r.tool}" has no action "${r.action}".`)}if(L(r)&&m!==e.steps.length-1){t.push(`${d}: reloading the app is only allowed as the last step.`)}if(!k(r.tool,r.action)){c.push(`${d}: ${r.tool}.${r.action} is an advanced step \u2014 its effects are not described or automatically undone.`)}});const o=new Set;for(const r of e.steps){for(const m of I(r,v(r.params,n))){o.add(m)}}let a=null;const u=i.activeScenario;if(u&&u.scenarioId!==e.id){const r=u.conflictKeys.filter(m=>[...o].some(d=>T(m,d)));if(r.length){a={activeScenarioId:u.scenarioId,activeName:u.name,overlaps:r}}}if(i.extraErrors?.length)t.push(...i.extraErrors);return{ok:t.length===0,errors:t,warnings:c,conflict:a,conflictKeys:[...o]}}function E(e,n,i,t){return new Promise((c,s)=>{const l=()=>{clearTimeout(f);s(new Error("Scenario cancelled"))};const f=setTimeout(()=>{t?.removeEventListener("abort",l);s(new Error(`${i} timed out after ${n}ms`))},n);if(t?.aborted)l();else t?.addEventListener("abort",l,{once:true});e.then(o=>{clearTimeout(f);t?.removeEventListener("abort",l);c(o)},o=>{clearTimeout(f);t?.removeEventListener("abort",l);s(o)})})}function b(e,n){return new Promise((i,t)=>{const c=()=>{clearTimeout(s);t(new Error("Scenario cancelled"))};const s=setTimeout(()=>{n?.removeEventListener("abort",c);i()},e);if(n?.aborted)c();else n?.addEventListener("abort",c,{once:true})})}async function D(e,n,i,t){const c=typeof e.route==="string"?e.route:null;const s=typeof e.element==="string"?e.element:null;if(!c&&!s)return;const l=Date.now()+i;let f="nothing";while(Date.now()<l){if(t?.aborted)throw new Error("Scenario cancelled");if(c){const o=await n("route-events","getCurrentRoute").catch(()=>null);if(o?.path===c)return;if(typeof o?.path==="string")f=o.path}else if(s){const o=await n("highlight-updates","describeScreen").catch(()=>null);const a=s.toLowerCase();const u=(o?.elements??[]).some(r=>[r.testID,r.text,r.label].some(m=>typeof m==="string"&&m.toLowerCase().includes(a)));if(u)return;f=`${o?.elements?.length??0} elements`}await b(O,t)}throw new Error(c?`Still on ${f} after ${i}ms \u2014 expected ${c}`:`\u201C${s}\u201D never appeared within ${i}ms`)}function R(e){if(e&&typeof e==="object"&&"ok"in e){const n=e;if(n.ok===false){return typeof n.error==="string"?n.error:"The step was rejected."}}return null}function x(e,n,i,t){if(e.tool==="network"&&e.action==="upsertOverrideRule"){const s=i;const l=s?.rule?.id;if(typeof l==="string"){return{kind:"override-rule",label:t,ruleId:l}}return null}if(e.tool==="impersonate"&&e.action==="startImpersonation"){const s=n.user;const l=typeof s?.id==="string"?s.id:void 0;return{kind:"impersonation",label:t,userId:l}}if(e.tool==="storage"){const[s,l=""]=e.action.split(".");const f=/^(setItem|removeItem|multiSet|multiRemove|clear|set|remove)$/.test(l);if(f){let o=typeof n.key==="string"?n.key:"";if(!o&&Array.isArray(n.pairs)){const a=n.pairs[0];o=Array.isArray(a)?String(a[0]??""):""}if(!o&&Array.isArray(n.keys))o=String(n.keys[0]??"");if(!o&&l==="clear")o="(all keys)";return{kind:"storage-write",label:t,backend:s,key:o}}}const c=M(e,n);if(c!==null){return{kind:"state-write",label:t,tool:e.tool,store:c}}if(e.tool===y)return null;return{kind:"transient",label:t,tool:e.tool,action:e.action}}function M(e,n){const i=t=>typeof n[t]==="string"?n[t]:"";if(e.tool==="zustand"&&e.action==="setState"){return i("storeName")||"a store"}if(e.tool==="jotai"&&e.action==="setAtom"){return i("atom")||i("atomName")||"an atom"}if(e.tool==="redux"&&e.action==="dispatch"){const t=n.action;return typeof t?.type==="string"?t.type:"the redux store"}return null}async function N(e,n){const i=e.user;const t=i?.id;if(!i||typeof t!=="string"&&typeof t!=="number")return e;const c=[i.email,i.name,i.displayName,t].filter(s=>typeof s==="string"||typeof s==="number");for(const s of c){try{const l=await n("impersonate","searchUsers",{query:String(s)});const f=Array.isArray(l)?l.find(o=>String(o?.id)===String(t)):void 0;if(f)return{...e,user:f}}catch{}}return e}export async function runScenario(e,n,i,t){const c=[];const s=[];const l=Date.now();let f=false;for(let o=0;o<e.steps.length;o++){const a=e.steps[o];const u=v(a.params??{},n);const r=$(a,u);if(f){c.push({id:a.id,label:r,ok:false,skipped:true,ms:0});continue}t?.onStep?.(o,"start");const m=Date.now();try{if(t?.signal?.aborted)throw new Error("Scenario cancelled");if(Date.now()-l>SCENARIO_TIMEOUT_MS){throw new Error("Scenario exceeded its overall time budget")}let d;if(a.tool===y&&a.action==="wait"){const w=typeof u.ms==="number"?u.ms:0;await b(Math.min(w,STEP_TIMEOUT_MS),t?.signal)}else if(a.tool===y&&a.action==="waitFor"){await E(D(u,i,a.timeoutMs??WAIT_FOR_TIMEOUT_MS,t?.signal),a.timeoutMs??WAIT_FOR_TIMEOUT_MS,r,t?.signal)}else{const w=a.tool==="impersonate"&&a.action==="startImpersonation"?await N(u,i):u;d=await E(Promise.resolve(i(a.tool,a.action,w)),a.timeoutMs??STEP_TIMEOUT_MS,r,t?.signal);const g=R(d);if(g)throw new Error(g)}const p=Date.now()-m;c.push({id:a.id,label:r,ok:true,ms:p});const h=x(a,u,d,r);if(h)s.push(h);t?.onStep?.(o,"ok")}catch(d){const p=Date.now()-m;const h=d instanceof Error?d.message:String(d);c.push({id:a.id,label:r,ok:false,error:h,ms:p});t?.onStep?.(o,"error",h);f=true}}return{ok:!f,scenarioId:e.id,name:String(A(e.name,n)),steps:c,effects:s}}export async function revertEffects(e,n){let i=0;let t=false;let c=0;const s=[];const l=[];const f=[];for(const o of e){try{if(o.kind==="override-rule"){await n("network","deleteOverrideRule",{id:o.ruleId});i++}else if(o.kind==="impersonation"){await n("impersonate","stopImpersonation",{});t=true}else if(o.kind==="storage-write"){if(o.key)s.push(o.key)}else if(o.kind==="state-write"){if(o.store)l.push(o.store)}else{c++}}catch(a){f.push(a instanceof Error?a.message:String(a))}}return{removedRules:i,stoppedImpersonation:t,clearedTransients:c,unrestoredStorageKeys:s,unrestoredState:l,errors:f}}
@@ -0,0 +1 @@
1
+ "use strict";export const UNGROUPED="Ungrouped";export const MAX_FOLDER_LENGTH=32;export function normalizeFolder(o){if(typeof o!=="string")return void 0;const n=o.replace(/\s+/g," ").trim().slice(0,MAX_FOLDER_LENGTH);return n||void 0}export function resolveFolder(o,n){const e=normalizeFolder(o);if(!e)return void 0;const t=n.find(r=>r.toLowerCase()===e.toLowerCase());return t??e}export function listFolders(o){const n=new Map;for(const e of o){const t=normalizeFolder(e.folder);if(!t)continue;const r=t.toLowerCase();if(!n.has(r))n.set(r,t)}return[...n.values()].sort((e,t)=>e.localeCompare(t,void 0,{sensitivity:"base"}))}export function inFolder(o,n){const e=normalizeFolder(o.folder);if(n===void 0)return!e;return!!e&&e.toLowerCase()===n.toLowerCase()}export function countInFolder(o,n){return o.reduce((e,t)=>inFolder(t,n)?e+1:e,0)}
@@ -0,0 +1 @@
1
+ "use strict";import{useSyncExternalStore as t}from"react";import{recorderStore as r}from"../record/recorderStore";import{scenariosStore as e}from"../store/scenariosStore";export function useScenariosState(){return t(e.subscribe,e.getState)}export function useActiveScenario(){return t(e.subscribe,()=>e.getState().active)}export function useScenariosRunState(){return t(e.subscribe,()=>e.getState().runState)}export function useRecording(){return t(r.subscribe,r.getState)}
@@ -0,0 +1 @@
1
+ "use strict";export{scenariosToolPreset,createScenariosTool,ScenariosIcon,SCENARIOS_ICON_COLOR}from"./preset";export{ScenariosBody}from"./components/ScenariosBody";export{ScenarioBanner,ScenariosOverlay}from"./components/ScenarioBanner";export{RecordingBanner}from"./components/RecordingBanner";export{MacroReviewScreen}from"./components/MacroReviewScreen";export{recorderStore}from"./record/recorderStore";export{condense,macroToSteps,describeTarget,selectorFor,humanizeTestID,titleFor}from"./record/condense";export{scenariosSyncAdapter}from"./sync/scenariosSyncAdapter";export{ScenariosToolView}from"./components/ScenariosToolView";export{scenariosStore,setRemoteScenariosHandler,replaceScenariosFromSnapshot,MAX_DEVICE_SCENARIOS}from"./store/scenariosStore";export{useScenariosState,useActiveScenario,useScenariosRunState}from"./hooks/useScenarios";export{defineScenario}from"./defineScenario";export{runScenario,revertEffects,preflight,STEP_TIMEOUT_MS,SCENARIO_TIMEOUT_MS}from"./engine";export{STEP_CATALOG,catalogEntry,describeStep,stepDurability,stepConflictKeys,conflictKeysOverlap,isAdvancedStep,DURABILITY_LABEL}from"./catalog";export{UNGROUPED,MAX_FOLDER_LENGTH,normalizeFolder,resolveFolder,listFolders,inFolder,countInFolder}from"./folders";export{interpolate,interpolateString,resolveVarValues,missingVarKeys,collectPlaceholders,coerceVarValue}from"./interpolate";import{scenariosStore as e}from"./store/scenariosStore";void e.ensureLoaded();
@@ -0,0 +1 @@
1
+ "use strict";const f=/\{\{\s*(\w+)\s*\}\}/g;const c=/^\{\{\s*(\w+)\s*\}\}$/;export function collectPlaceholders(r,t=new Set){if(typeof r==="string"){for(const e of r.matchAll(f))t.add(e[1]);return t}if(Array.isArray(r)){for(const e of r)collectPlaceholders(e,t);return t}if(r&&typeof r==="object"){for(const e of Object.values(r))collectPlaceholders(e,t)}return t}export function interpolateString(r,t){const e=r.match(c);if(e&&e[1]in t)return t[e[1]];return r.replace(f,(n,o)=>o in t?String(t[o]):n)}export function interpolate(r,t){if(typeof r==="string")return interpolateString(r,t);if(Array.isArray(r))return r.map(e=>interpolate(e,t));if(r&&typeof r==="object"){const e={};for(const[n,o]of Object.entries(r))e[n]=interpolate(o,t);return e}return r}export function resolveVarValues(r,t={}){const e={};for(const n of r)if(n.default!==void 0)e[n.key]=n.default;for(const[n,o]of Object.entries(t)){if(o!==void 0)e[n]=o}return e}export function missingVarKeys(r,t){return r.filter(e=>!(e.key in t)).map(e=>e.key)}export function coerceVarValue(r,t){if(r.type==="number"){const e=typeof t==="number"?t:Number(t);return Number.isFinite(e)?e:0}if(r.type==="boolean"){return typeof t==="boolean"?t:t==="true"||t===1}return t}
@@ -0,0 +1 @@
1
+ "use strict";import{ScenariosIcon as o,SCENARIOS_ICON_COLOR as r}from"@buoy-gg/floating-tools-core";import{ScenariosBody as t}from"./components/ScenariosBody";import{scenariosStore as n}from"./store/scenariosStore";import{jsx as s}from"react/jsx-runtime";export{o as ScenariosIcon,r as SCENARIOS_ICON_COLOR};export const scenariosToolPreset={id:"scenarios",name:"SCENARIOS",description:"One-tap app states for QA, support and demos",slot:"both",icon:({size:e,color:a})=>s(o,{size:e,color:a||r}),component:t,modal:{subtitle:"saved one-tap app states",searchable:true,searchPlaceholder:"Search name, tag, author\u2026"}};export function createScenariosTool(e={}){if(e.scenarios?.length){n.registerCodeScenarios(e.scenarios)}return{...scenariosToolPreset,name:e.name??scenariosToolPreset.name,description:e.description??scenariosToolPreset.description,component:()=>s(t,{currentBuild:e.currentBuild})}}
@@ -0,0 +1 @@
1
+ "use strict";export const UI_TOOL="highlight-updates";export const ROUTE_TOOL="route-events";export const STATE_TOOL="zustand";const p=new Set(["item","row","card","btn","button","cta","link","cell","tile","popular","open","tap","press","go"]);export function humanizeTestID(e){const t=e.split(/[-_.]/).filter(Boolean);const n=t.length>=3?t.slice(1):t;const o=n.filter(s=>!p.has(s.toLowerCase()));const i=o.length?o:n;const r=i.join(" ").trim();if(!r)return e;return r.charAt(0).toUpperCase()+r.slice(1)}const f=28;export function describeTarget(e){const t=e.testID?humanizeTestID(e.testID):void 0;const n=e.control==="text";const o=n?[e.label,t,e.text]:[e.label,e.text,t];const i=o.filter(s=>typeof s==="string"&&s.trim().length>0);const r=i.find(s=>s.trim().length<=f);if(r)return r.trim();if(i.length)return c(i[0].trim(),f);return e.name}export function selectorFor(e){if(e.testID)return{testID:e.testID};const t=e.label??e.text;if(t&&t.trim())return{query:t.trim()};return null}function c(e,t=32){const n=typeof e==="string"?e:JSON.stringify(e);if(typeof n!=="string")return"";return n.length>t?`${n.slice(0,t)}\u2026`:n}export function titleFor(e){const{touch:t}=e;const n=describeTarget(t.target);switch(t.kind){case"text":return`Type \u201C${c(t.value)}\u201D into ${n}`;case"toggle":return`${t.value===false?"Turn off":"Turn on"} ${n}`;case"slider":return`Set ${n} to ${c(t.value)}`;case"longPress":return`Press and hold \u201C${n}\u201D`;default:return`Tap \u201C${n}\u201D`}}export function summarizeState(e){if(e==null||typeof e!=="object")return void 0;const t=[];for(const[n,o]of Object.entries(e)){if(typeof o==="function")continue;if(Array.isArray(o)){t.push(`${o.length} ${n}`)}else if(o==null||typeof o!=="object"){if(o===null||o===""||o===false||o===0)continue;t.push(`${n} ${c(o,16)}`)}if(t.length>=3)break}return t.length?t.join(", "):void 0}export function detailFor(e){const t=[];if(e.effects.route&&e.effects.route!==e.routeBefore){t.push(`went to ${e.effects.route}`)}for(const n of e.effects.stores??[]){const o=summarizeState(n.state);t.push(o?`${n.name} \u2192 ${o}`:`changed ${n.name}`)}return t.length?t.join(" \xB7 "):void 0}let d=0;const a=e=>`${e}-${Date.now().toString(36)}-${(d++).toString(36)}`;function h(e){const t=selectorFor(e.touch.target);if(!t)return[];const n={...t};const{touch:o}=e;if(o.kind==="longPress")n.longPress=true;if(o.kind==="text")n.text=o.value;if(o.kind==="toggle"||o.kind==="slider")n.value=o.value;const i=[{id:a("ui"),tool:UI_TOOL,action:"tapElement",params:n,label:titleFor(e)}];const r=e.effects.route;if(r&&r!==e.routeBefore){i.push({id:a("wait"),tool:"scenario",action:"waitFor",params:{route:r},label:`Wait for ${r}`})}return i}function g(e){const t=[];for(const o of e.effects.stores??[]){t.push({id:a("state"),tool:STATE_TOOL,action:"setState",params:{storeName:o.name,state:o.state,replace:true,persisted:o.persisted??false},label:`Set ${o.name}`})}const n=e.effects.route;if(n&&n!==e.routeBefore){t.push({id:a("nav"),tool:ROUTE_TOOL,action:"navigate",params:{path:n},label:`Go to ${n}`})}return t}function m(e){const t={id:a("start"),tool:ROUTE_TOOL,action:"navigate",params:{path:e,reset:true},label:`Go to ${e}`};return{id:a("pre"),precondition:true,title:`Start from ${e}`,detail:"so it can run from anywhere",lane:"state",lanes:["state"],enabled:true,noise:false,ui:[],state:[t]}}export function condense(e,t){const n=e.map(o=>{const i=h(o);const r=g(o);const s=[];if(i.length)s.push("ui");if(r.length)s.push("state");return{id:o.id,title:titleFor(o),detail:detailFor(o),lane:s.includes("ui")?"ui":"state",lanes:s,enabled:s.length>0,noise:s.length===0,ui:i,state:r}});if(t&&n.length)return[m(t),...n];return n}function b(e){if(e.tool===STATE_TOOL&&e.action==="setState"){const t=e.params?.storeName;return t?`${STATE_TOOL}:${t}`:null}if(e.tool===ROUTE_TOOL&&e.action==="navigate")return"route";return null}export function macroToSteps(e){const t=[];let n=new Map;let o=0;const i=()=>{for(const r of n.values())t.push(r);n=new Map;o=0};for(const r of e){if(!r.enabled||r.noise)continue;const s=r.lane==="state"?r.state:r.ui;if(!s.length)continue;if(r.lane!=="state"){i();t.push(...s);continue}for(const l of s){const u=b(l);if(u==null){n.set(`\0${o++}`,l);continue}n.delete(u);n.set(u,l)}}i();return t}export function fastForwardTo(e,t){return e.map((n,o)=>{if(n.precondition)return n;if(o>t){return{...n,lane:"ui",enabled:!n.noise&&n.lanes.length>0}}if(n.lanes.includes("state")){return{...n,lane:"state",enabled:true}}return{...n,enabled:false}})}export function replayAll(e){return e.map(t=>t.precondition?t:{...t,lane:t.lanes.includes("ui")?"ui":"state",enabled:t.lanes.length>0&&!t.noise})}export function fastForwardIndex(e){let t=-1;for(let n=0;n<e.length;n++){const o=e[n];if(o.precondition)continue;if(o.enabled&&o.lane==="state"){t=n;continue}if(!o.enabled)continue;break}return t}const $=/^(continue|next|ok|done|submit|save|confirm|apply|back|close|cancel|open|add\b|view\b|go to\b|place order|checkout|check out)/i;export function suggestMacroName(e){const t=e.filter(s=>s.enabled&&!s.noise);let n;for(const s of t){for(const l of s.state??[]){const u=l.params?.path;if(l.tool===ROUTE_TOOL&&l.action==="navigate"&&typeof u==="string")n=u}}const o=n?.split("?")[0].split("/").filter(Boolean).pop();const i=n===void 0?"":o?x(o.replace(/[-_]/g," ")):"Home";let r="";for(const s of[...t].reverse()){if(s.precondition)continue;const l=s.title.match(/“([^”]+)”/)?.[1]?.split(",")[0].trim();if(l&&!$.test(l)){r=l;break}}if(i&&r)return`${i} \xB7 ${r}`;if(i||r)return i||r;return t.length?`${t.length} recorded step${t.length===1?"":"s"}`:""}function x(e){return e.replace(/\b\w/g,t=>t.toUpperCase())}
@@ -0,0 +1 @@
1
+ "use strict";import{dispatchToolAction as m,getToolAdapters as g}from"@buoy-gg/shared-ui";import{condense as T,ROUTE_TOOL as S,STATE_TOOL as l,UI_TOOL as n}from"./condense";const y=250;const C=1200;const p={status:"idle",reason:null,startedAt:null,startRoute:void 0,actions:[],draft:null};let R=0;const b=()=>`act-${Date.now().toString(36)}-${(R++).toString(36)}`;class v{state=p;listeners=new Set;timer=null;sinceSeq=0;storeChanges=new Map;holds=[];ticking=false;getState=()=>this.state;subscribe=t=>{this.listeners.add(t);return()=>{this.listeners.delete(t)}};setState(t){this.state={...this.state,...t};this.listeners.forEach(e=>{try{e()}catch{}})}canRecord(){const t=g();return typeof t[n]?.actions?.startTouchCapture==="function"}async start(){if(this.state.status==="recording")return;if(!this.canRecord()){this.setState({status:"unavailable",reason:"Recording needs the Highlight Updates tool installed \u2014 it owns the screen reader this uses to name what you tap."});return}this.holds=[];for(const e of[S,l]){const s=g()[e]?.subscribe;if(s){try{this.holds.push(s(()=>{}))}catch{}}}await this.call(n,"clearTouchCapture");const t=await this.call(n,"startTouchCapture");if(t?.status!=="capturing"){this.releaseHolds();this.setState({status:"unavailable",reason:t?.reason??"This build of React Native doesn't expose the touch stream Buoy records from."});return}this.sinceSeq=0;this.route=await this.readRoute();this.storeChanges=await this.readStoreChanges();this.setState({status:"recording",reason:null,startedAt:Date.now(),startRoute:this.route,actions:[],draft:null});this.timer=setInterval(()=>void this.tick(),y)}async stop(){if(this.state.status!=="recording")return;this.clearTimer();await new Promise(t=>setTimeout(t,C));await this.tick();await this.call(n,"stopTouchCapture");this.releaseHolds();this.setState({status:"review",draft:T(this.state.actions,this.state.startRoute)})}async cancel(){this.clearTimer();if(this.state.status==="recording"){await this.call(n,"stopTouchCapture")}this.releaseHolds();this.setState({...p})}setDraft(t){if(this.state.status!=="review")return;this.setState({draft:t})}clearTimer(){if(this.timer){clearInterval(this.timer);this.timer=null}}releaseHolds(){this.holds.forEach(t=>{try{t()}catch{}});this.holds=[]}async tick(){if(this.ticking)return;this.ticking=true;try{const t=await this.call(n,"readTouchCapture",{sinceSeq:this.sinceSeq});const e=[...this.state.actions];let s=false;for(const a of t?.records??[]){e.push({id:b(),touch:a,routeBefore:this.route,effects:{}});s=true}if(t?.seq)this.sinceSeq=t.seq;const i=e[e.length-1];const c=await this.readRoute();if(c!==void 0&&c!==this.route){this.route=c;if(i){i.effects={...i.effects,route:c};s=true}}const d=await this.readStoreChanges();const h=[];for(const[a,r]of d){const o=this.storeChanges.get(a);if(o!==void 0&&o.changes!==r.changes){h.push({name:a,persisted:r.persisted})}}this.storeChanges=d;if(i&&h.length){const a=[...i.effects.stores??[]];for(const{name:r,persisted:o}of h){const u=await this.readStoreState(r);if(u===void 0)continue;const f=a.findIndex(w=>w.name===r);if(f>=0)a[f]={name:r,state:u,persisted:o};else a.push({name:r,state:u,persisted:o})}if(a.length){i.effects={...i.effects,stores:a};s=true}}if(s)this.setState({actions:e})}finally{this.ticking=false}}async call(t,e,s){try{return await m(t,e,s)}catch{return null}}async readRoute(){const t=await this.call(S,"getCurrentRoute");const e=t?.path;return typeof e==="string"?e:void 0}async readStoreChanges(){const t=await this.call(l,"listStores",{});const e=new Map;for(const s of t?.stores??[]){if(!s?.name)continue;e.set(s.name,{changes:s.changes??0,persisted:s.isPersisted===true})}return e}async readStoreState(t){const e=await this.call(l,"getStoreState",{storeName:t});if(!e?.found)return void 0;return e.currentState}}export const recorderStore=new v;
@@ -0,0 +1 @@
1
+ "use strict";export{};
@@ -0,0 +1 @@
1
+ "use strict";import{devToolsStorageKeys as y,persistentStorage as h,dispatchToolAction as u,assertAccountAccess as v,hasAccountAccess as m,getToolAdapters as g,listToolActions as D,listToolIds as A,waitForToolRegistry as I}from"@buoy-gg/shared-ui";import{preflight as L,revertEffects as p,runScenario as V}from"../engine";import{stepConflictKeys as C}from"../catalog";import{withStepIds as b}from"../defineScenario";import{listFolders as k,resolveFolder as E}from"../folders";import{interpolate as _,interpolateString as S,resolveVarValues as R}from"../interpolate";const x=y.scenarios.library();const w=y.scenarios.active();export const MAX_DEVICE_SCENARIOS=100;let l=null;export function setRemoteScenariosHandler(f){l=f}function T(){return!(typeof __DEV__!=="undefined"&&!__DEV__)}class K{state={code:[],device:[],drafts:[],active:null,runState:null,lastDeactivateReport:null,hasLoaded:false,lastVarValues:{}};listeners=new Set;loadPromise=null;expiryTimer=null;runController=null;getState=()=>this.state;subscribe=t=>{this.listeners.add(t);void this.ensureLoaded();return()=>{this.listeners.delete(t)}};setState(t){this.state={...this.state,...t};this.listeners.forEach(e=>{try{e()}catch{}})}allScenarios(){const t=new Set(this.state.code.map(e=>e.id));return[...this.state.code,...this.state.device.filter(e=>!t.has(e.id))]}find(t){return this.allScenarios().find(e=>e.id===t)??this.state.drafts.find(e=>e.id===t)}folders(){return k([...this.state.code,...this.state.device,...this.state.drafts])}registerCodeScenarios(t){const e=new Map(this.state.code.map(r=>[r.id,r]));for(const r of t)e.set(r.id,{...r,source:"code"});this.setState({code:[...e.values()]})}ensureLoaded(){if(l)return Promise.resolve();if(!this.loadPromise){this.loadPromise=this.load()}return this.loadPromise}async load(){let t=[];let e=[];let r={};try{const a=await h.getItem(x);if(a){const i=JSON.parse(a);t=Array.isArray(i.device)?i.device:[];e=Array.isArray(i.drafts)?i.drafts:[];r=i.lastVarValues&&typeof i.lastVarValues==="object"?i.lastVarValues:{}}}catch{}let s=null;try{const a=await h.getItem(w);if(a)s=JSON.parse(a)}catch{s=null}this.setState({device:t,drafts:e,lastVarValues:r,active:s,hasLoaded:true});if(s)await this.reconcileActive()}async persistLibrary(){if(l)return;const t={version:1,device:this.state.device,drafts:this.state.drafts,lastVarValues:this.state.lastVarValues};try{await h.setItem(x,JSON.stringify(t))}catch{}}async persistActive(){if(l)return;try{if(this.state.active){await h.setItem(w,JSON.stringify(this.state.active))}else{await h.removeItem(w)}}catch{}}async reconcileActive(){const t=this.state.active;if(!t)return;const e=t.effects.filter(r=>r.kind==="override-rule");if(!e.length)return;if(!A().includes("network"))return;try{const r=await u("network","listOverrideRules",{});const s=new Set((r?.rules??[]).map(i=>i.id));const a=t.effects.filter(i=>i.kind!=="override-rule"||s.has(i.ruleId));if(a.length===t.effects.length)return;if(!a.length){this.setState({active:null})}else{this.setState({active:{...t,effects:a}})}await this.persistActive()}catch{}}checkBeforeRun(t,e,r=[]){const s=this.state.active;const a=[...r];if(!T()){a.push("Scenarios cannot run in a production build.")}const i=R(t.vars,e);return L(t,i,{availableTools:A(),availableActions:D(),activeScenario:s?{scenarioId:s.scenarioId,name:s.name,conflictKeys:this.activeConflictKeys()}:null,extraErrors:a})}holdObservers(t){const e=[];const r=g();for(const s of t){const a=r[s]?.subscribe;if(!a)continue;try{e.push(a(()=>{}))}catch{}}return e}activeConflictKeys(){const t=this.state.active;if(!t)return[];const e=this.find(t.scenarioId);if(!e)return[];const r=new Set;for(const s of e.steps){for(const a of C(s,_(s.params,t.varValues))){r.add(a)}}return[...r]}async run(t,e={},r={}){v();if(l){return await l("run",{id:t,params:e})}await this.ensureLoaded();v();const s=this.find(t);if(!s)throw new Error(`No scenario "${t}"`);if(s.source==="draft"){throw new Error("Drafts must be accepted before they can run.")}if(!T()){throw new Error("Scenarios cannot run in a production build.")}if(!await I()){throw new Error("Buoy tools are still starting up \u2014 try again in a moment.")}const a=R(s.vars,e);if(this.state.active&&this.state.active.scenarioId!==t){await this.deactivate()}else if(this.state.active){const n=this.state.active;await p(n.effects.filter(d=>d.kind==="override-rule"),u);this.setState({active:{...n,effects:n.effects.filter(d=>d.kind!=="override-rule")}})}v();this.setState({runState:{scenarioId:t,name:S(s.name,a),steps:[],current:0,done:false}});const i=this.holdObservers(["route-events"]);let c;this.runController?.abort();const o=new AbortController;this.runController=o;try{c=await V(s,a,u,{signal:o.signal,onStep:n=>{const d=this.state.runState;if(d)this.setState({runState:{...d,current:n}})}})}finally{if(this.runController===o)this.runController=null;i.forEach(n=>{try{n()}catch{}})}this.setState({runState:{scenarioId:t,name:S(s.name,a),steps:c.steps,current:-1,done:true,report:c}});if(c.ok&&m()){const n={scenarioId:t,name:S(s.name,a),version:s.version,source:s.source,runAt:Date.now(),varValues:a,effects:c.effects,expiresAt:r.expiresInMs?Date.now()+r.expiresInMs:void 0};this.setState({active:n,lastDeactivateReport:null,lastVarValues:{...this.state.lastVarValues,[t]:a}});this.bumpUsage(t);this.armExpiry();await this.persistActive();await this.persistLibrary()}return c}async undoPartialRun(){const t=this.state.runState?.report;if(!t){return{removedRules:0,stoppedImpersonation:false,clearedTransients:0,unrestoredStorageKeys:[],unrestoredState:[],undoStepsRan:0,errors:[]}}const e=await p(t.effects,u);const r={...e,undoStepsRan:0};this.setState({runState:null,lastDeactivateReport:r});return r}clearRunState(){this.setState({runState:null})}bumpUsage(t){const e=r=>r.map(s=>s.id===t?{...s,usage:{runs:(s.usage?.runs??0)+1,lastRunAt:Date.now()}}:s);this.setState({code:e(this.state.code),device:e(this.state.device)})}async deactivate(){if(l){return await l("deactivate",{})}const t=this.state.active;if(!t){const o={removedRules:0,stoppedImpersonation:false,clearedTransients:0,unrestoredStorageKeys:[],unrestoredState:[],undoStepsRan:0,errors:[]};return o}const e=await p(t.effects,u);const r=[...e.errors];let s=0;const a=this.find(t.scenarioId);const i=a?.undoSteps??[];if(i.length){const o=await V({...a,steps:i},t.varValues,u);s=o.steps.filter(n=>n.ok).length;for(const n of o.steps){if(!n.ok&&n.error)r.push(n.error)}}const c={removedRules:e.removedRules,stoppedImpersonation:e.stoppedImpersonation,clearedTransients:e.clearedTransients,unrestoredStorageKeys:s?[]:e.unrestoredStorageKeys,unrestoredState:s?[]:e.unrestoredState,undoStepsRan:s,errors:r};this.clearExpiry();this.setState({active:null,lastDeactivateReport:c});await this.persistActive();return c}async disposeAccountSession(t){this.runController?.abort();this.runController=null;if(m())return;this.clearExpiry();const e=this.state.active;if(!e||l)return;const r=await p(e.effects.filter(s=>s.kind==="override-rule"),t);this.setState({active:null});await this.persistActive();if(r.errors.length)throw new Error(r.errors.join("; "))}dismissDeactivateReport(){this.setState({lastDeactivateReport:null})}armExpiry(){this.clearExpiry();const t=this.state.active?.expiresAt;if(!t)return;const e=Math.max(0,t-Date.now());this.expiryTimer=setTimeout(()=>{void this.deactivate()},e)}clearExpiry(){if(this.expiryTimer){clearTimeout(this.expiryTimer);this.expiryTimer=null}}isLongRunning(t=8*60*60*1e3){const e=this.state.active;return!!e&&Date.now()-e.runAt>t}async saveScenario(t,e={}){await this.ensureLoaded();const r=Date.now();const s=e.asDraft??false;const a=s?this.state.drafts:this.state.device;const i=a.findIndex(n=>n.id===t.id);const c={...t,steps:b(t.steps,"s"),undoSteps:t.undoSteps?b(t.undoSteps,"u"):void 0,folder:E(t.folder,this.folders()),source:s?"draft":"device",version:i>=0?(a[i].version??1)+1:t.version||1,createdAt:i>=0?a[i].createdAt:r,updatedAt:r,usage:i>=0?a[i].usage:{runs:0}};if(!s&&i<0&&this.state.device.length>=MAX_DEVICE_SCENARIOS){throw new Error(`This device already holds ${MAX_DEVICE_SCENARIOS} scenarios \u2014 delete one first.`)}const o=[...a];if(i>=0)o[i]=c;else o.unshift(c);this.setState(s?{drafts:o}:{device:o});await this.persistLibrary();return c}async acceptDraft(t){await this.ensureLoaded();const e=this.state.drafts.find(s=>s.id===t);if(!e)throw new Error(`No draft "${t}"`);const r={...e,source:"device",unsavedToRepo:true,updatedAt:Date.now()};this.setState({drafts:this.state.drafts.filter(s=>s.id!==t),device:[r,...this.state.device]});await this.persistLibrary();return r}async discardDraft(t){await this.ensureLoaded();this.setState({drafts:this.state.drafts.filter(e=>e.id!==t)});await this.persistLibrary()}async setFolder(t,e){await this.ensureLoaded();if(l){const o=await l("setFolder",{id:t,folder:e});return o.scenario}if(this.state.code.some(o=>o.id===t)){throw new Error("Scenarios shipped with the app are filed in code \u2014 change `folder` in defineScenario().")}const r=E(e,this.folders());let s;const a=o=>o.map(n=>{if(n.id!==t)return n;s={...n,folder:r,updatedAt:Date.now()};return s});const i=a(this.state.device);const c=s?this.state.drafts:a(this.state.drafts);if(!s)throw new Error(`No scenario "${t}"`);this.setState({device:i,drafts:c});await this.persistLibrary();return s}async deleteScenario(t){await this.ensureLoaded();if(this.state.active?.scenarioId===t)await this.deactivate();this.setState({device:this.state.device.filter(e=>e.id!==t)});await this.persistLibrary()}rememberVarValues(t,e){this.setState({lastVarValues:{...this.state.lastVarValues,[t]:e}});void this.persistLibrary()}lastValuesFor(t){return this.state.lastVarValues[t]??{}}replaceFromSnapshot(t){this.setState({code:t.code??[],device:t.device??[],drafts:t.drafts??[],active:t.active??null,runState:t.runState??null,hasLoaded:true})}}export const scenariosStore=new K;export function replaceScenariosFromSnapshot(f){scenariosStore.replaceFromSnapshot(f)}export function installedToolIds(){return Object.keys(g())}
@@ -0,0 +1 @@
1
+ "use strict";import{describeStep as d,stepDurability as u}from"../catalog";import{interpolate as h,resolveVarValues as v}from"../interpolate";import{scenariosStore as i}from"../store/scenariosStore";const m=64*1024;const w=256*1024;function s(e,r){const t=e?.[r];return typeof t==="string"&&t.trim()?t.trim():void 0}function f(e){const r=e?.params;if(!r||typeof r!=="object")return{};const t={};for(const[o,n]of Object.entries(r)){if(typeof n==="string"||typeof n==="number"||typeof n==="boolean"){t[o]=n}}return t}const g=8*1024;function c(e){return{id:e.id,name:e.name,description:e.description,version:e.version,source:e.source,author:e.author,folder:e.folder,tags:e.tags,vars:e.vars,stepCount:e.steps.length,expectedOutcome:e.expectedOutcome,authoredAgainst:e.authoredAgainst,verifiedAt:e.verifiedAt,unsavedToRepo:e.unsavedToRepo,usage:e.usage,createdAt:e.createdAt,updatedAt:e.updatedAt,steps:e.steps.map(r=>{const t=JSON.stringify(r.params??{}).length;return t>g?{...r,params:void 0,paramsOmitted:true}:r}),stepLines:e.steps.map(r=>({label:d(r),durability:u(r)}))}}function S(e){const r=[];if(!e.id||typeof e.id!=="string"){r.push("A scenario needs a string `id`.")}if(!e.name||typeof e.name!=="string"){r.push("A scenario needs a `name`.")}if(!Array.isArray(e.steps)||!e.steps.length){r.push("A scenario needs at least one step.")}else{e.steps.forEach((o,n)=>{if(!o||typeof o!=="object"){r.push(`Step ${n+1} is not an object.`);return}if(!o.tool||!o.action){r.push(`Step ${n+1} needs both \`tool\` and \`action\`.`)}const a=JSON.stringify(o.params??{}).length;if(a>m){r.push(`Step ${n+1} is ${Math.round(a/1024)}KB \u2014 over the 64KB limit. Use a smaller response body.`)}})}if(e.vars&&!Array.isArray(e.vars)){r.push("`vars` must be an array.")}const t=JSON.stringify(e).length;if(t>w){r.push(`The scenario is ${Math.round(t/1024)}KB \u2014 over the 256KB limit.`)}return r}function p(){const e=i.getState();return{code:e.code.map(c),device:e.device.map(c),drafts:e.drafts.map(c),active:e.active,runState:e.runState,hasLoaded:e.hasLoaded,folders:i.folders()}}export const scenariosSyncAdapter={dispose:e=>i.disposeAccountSession(e),version:1,getSnapshot:()=>p(),subscribe:e=>i.subscribe(e),actions:{listScenarios:()=>p(),getScenario:e=>{const r=s(e,"id");if(!r)throw new Error("getScenario requires an `id`");const t=i.find(r);if(!t)throw new Error(`No scenario "${r}"`);return{scenario:t}},save:async e=>{const r=e??{};const t=r.scenario;if(!t)return{ok:false,error:"save requires a `scenario`."};const o=S(t);if(o.length)return{ok:false,errors:o};const n=Date.now();const a={id:t.id,name:t.name,description:t.description,version:t.version??1,source:"draft",author:t.author??"remote",vars:t.vars??[],steps:t.steps,undoSteps:t.undoSteps,folder:t.folder,tags:t.tags,expectedOutcome:t.expectedOutcome,authoredAgainst:t.authoredAgainst,unsavedToRepo:true,createdAt:n,updatedAt:n,usage:{runs:0}};const l=await i.saveScenario(a,{asDraft:true});return{ok:true,savedAsDraft:true,id:l.id,message:"Saved as a draft. Open Scenarios on the device to review and accept it."}},acceptDraft:async e=>{const r=s(e,"id");if(!r)throw new Error("acceptDraft requires an `id`");const t=await i.acceptDraft(r);return{ok:true,id:t.id}},discardDraft:async e=>{const r=s(e,"id");if(!r)throw new Error("discardDraft requires an `id`");await i.discardDraft(r);return{ok:true,id:r}},preview:e=>{const r=s(e,"id");if(!r)throw new Error("preview requires an `id`");const t=i.find(r);if(!t)throw new Error(`No scenario "${r}"`);const o=v(t.vars,f(e));const n=i.checkBeforeRun(t,o);return{willDo:t.steps.map(a=>({label:d(a,h(a.params??{},o)),durability:u(a)})),values:o,ok:n.ok,errors:n.errors,warnings:n.warnings,conflict:n.conflict,expectedOutcome:t.expectedOutcome}},run:async e=>{const r=s(e,"id");if(!r)throw new Error("run requires an `id`");const t=f(e);const o=i.find(r);if(!o)throw new Error(`No scenario "${r}"`);const n=i.checkBeforeRun(o,t);if(!n.ok){return{ok:false,preflightErrors:n.errors,conflict:n.conflict}}return await i.run(r,t)},deactivate:async()=>{const e=await i.deactivate();return{ok:true,report:e}},delete:async e=>{const r=s(e,"id");if(!r)throw new Error("delete requires an `id`");await i.deleteScenario(r);return{ok:true,id:r}},listFolders:()=>({folders:i.folders()}),setFolder:async e=>{const r=s(e,"id");if(!r)throw new Error("setFolder requires an `id`");const t=s(e,"folder");const o=await i.setFolder(r,t);return{ok:true,id:o.id,folder:o.folder??null}},getActive:()=>({active:i.getState().active}),export:e=>{const r=s(e,"id");if(!r)throw new Error("export requires an `id`");const t=i.find(r);if(!t)throw new Error(`No scenario "${r}"`);return{json:JSON.stringify(t,null,2)}}}};
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1 @@
1
+ "use strict";import{StyleSheet as d,Text as g,View as a}from"react-native";import{SectionHeader as c,Pill as s}from"@buoy-gg/shared-ui";import{scn as o}from"./scnTheme";import{jsx as l,jsxs as p}from"react/jsx-runtime";export function SectionLabel({children:t,icon:r,count:e,color:i=o.color.accent,testID:u,countTestID:f}){return l(a,{testID:u,collapsable:false,children:p(c,{children:[r?l(c.Icon,{icon:r,color:i,size:14}):null,l(c.Title,{children:t}),e!==void 0?l(c.Badge,{count:e,color:i,testID:f}):null]})})}export function Footnote({children:t,centered:r,tone:e="secondary",testID:i}){return l(g,{style:[n.footnote,r?n.footnoteCentered:null,e==="warning"?n.footnoteWarning:null,e==="danger"?n.footnoteDanger:null],testID:i,children:t})}export function Panel({children:t,tone:r="neutral",style:e,testID:i}){return l(a,{style:[n.panel,r==="sim"?n.panelSim:null,r==="danger"?n.panelDanger:null,r==="info"?n.panelInfo:null,e],testID:i,children:t})}export function PanelSeparator(){return l(a,{style:n.panelSeparator})}export function Notice({children:t,tone:r,testID:e}){return l(a,{style:[n.notice,r==="sim"?n.noticeSim:null,r==="danger"?n.noticeDanger:null,r==="info"?n.noticeInfo:null],testID:e,children:t})}export function PillLabel({label:t,color:r,testID:e}){return l(s,{label:t,color:r,variant:"tinted",style:{height:26,paddingHorizontal:10,borderRadius:o.radius.pill,justifyContent:"center"},testID:e})}export function DurabilityChip({label:t,tone:r,testID:e}){const i=r==="durable"?o.color.accent:r==="transient"?o.color.sim:o.color.textTertiary;return l(s,{label:t,color:i,variant:"tinted",mono:true,uppercase:false,testID:e})}const n=d.create({footnote:{color:o.color.textTertiary,fontSize:o.font.footnote,lineHeight:o.font.footnote*1.45,paddingHorizontal:o.gutter+4,paddingTop:8},footnoteCentered:{textAlign:"center"},footnoteWarning:{color:o.color.sim},footnoteDanger:{color:o.color.danger},panel:{marginHorizontal:o.gutter,marginVertical:4,backgroundColor:o.color.surface,borderRadius:o.radius.card,borderWidth:1,borderColor:o.color.border,overflow:"hidden"},panelSim:{borderColor:o.color.simBorder},panelDanger:{borderColor:o.color.dangerBorder},panelInfo:{borderColor:o.color.infoBorder},panelSeparator:{height:d.hairlineWidth,marginLeft:o.textInset,backgroundColor:o.color.separator},notice:{marginHorizontal:o.gutter,marginTop:8,borderRadius:o.radius.card,borderWidth:1,paddingHorizontal:o.textInset,paddingVertical:10,gap:3},noticeSim:{backgroundColor:o.color.simSoft,borderColor:o.color.simBorder},noticeDanger:{backgroundColor:o.color.dangerSoft,borderColor:o.color.dangerBorder},noticeInfo:{backgroundColor:o.color.infoSoft,borderColor:o.color.infoBorder}});
@@ -0,0 +1 @@
1
+ "use strict";import{night as o}from"@buoy-gg/shared-ui";import{monoFont as r}from"@buoy-gg/shared-ui";export const scn={color:{bg:o.color.bg,surface:"rgba(12, 12, 12, 0.9)",row:o.color.surfaceElevated,chip:o.color.buttonSurface,border:o.color.border,separator:o.color.separator,fill:o.color.fill,text:o.color.text,textSecondary:o.color.textSecondary,textTertiary:o.color.textTertiary,accent:o.color.accent,accentSoft:o.color.accentSoft,accentBorder:o.color.accentBorder,accentBorderStrong:o.color.accentBorderStrong,onAccent:o.color.onAccent,sim:o.color.warning,simSoft:o.color.warningSoft,simBorder:"rgba(255, 131, 41, 0.35)",onSim:"#000000",danger:o.color.danger,dangerSoft:o.color.dangerSoft,dangerBorder:"rgba(255, 59, 48, 0.30)",info:o.color.info,infoSoft:o.color.infoSoft,infoBorder:"rgba(30, 109, 255, 0.25)"},radius:{card:8,row:6,badge:4,pill:6},gutter:12,textInset:12,rowMinHeight:44,font:{row:13,control:12,label:11,footnote:11,mono:11,micro:9}};export const SCN_MONO=r;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The step catalog — plain English, durability, and conflict keys.
3
+ *
4
+ * The ENGINE will run any `{tool, action, params}` the action registry knows
5
+ * about. This catalog is the curated subset the UI can *describe*: the verbs
6
+ * docs teach, the ones a run form validates eagerly, and the ones whose
7
+ * effects the runner knows how to reverse.
8
+ *
9
+ * Three things every surface reads from here, so they can never disagree:
10
+ * 1. `describeStep` — the sentence QA reads before tapping Run, the line
11
+ * MCP prints, and the row the desktop panel renders. ONE humanizer.
12
+ * 2. `durability` — whether an effect survives a refetch, a restart, or
13
+ * neither. Never hidden, never softened.
14
+ * 3. `conflictKeys` — what a step *touches*, so two scenarios fighting over
15
+ * the same endpoint is refused loudly instead of last-write-wins.
16
+ *
17
+ * A step outside the catalog still runs; it just renders as `tool.action` with
18
+ * an "advanced" chip. The UI does not pretend to understand it.
19
+ */
20
+ import type { Durability, ScenarioStep } from "./types";
21
+ /** Engine-local pseudo-tool — never dispatched to an adapter. */
22
+ export declare const SCENARIO_TOOL = "scenario";
23
+ export interface CatalogEntry {
24
+ tool: string;
25
+ action: string;
26
+ /** Menu title when building a scenario. */
27
+ title: string;
28
+ /**
29
+ * A function when the same verb has different reach depending on its target:
30
+ * writing a zustand store that persists survives a restart, and writing one
31
+ * that doesn't is gone at launch. Calling both "durable" over-promises, and
32
+ * calling both "session" tells a tester their state is gone when it isn't —
33
+ * so the honest answer has to be allowed to depend on the params.
34
+ */
35
+ durability: Durability | ((params: Record<string, unknown>) => Durability);
36
+ /** Plain-English sentence for a resolved (already interpolated) params object. */
37
+ describe: (params: Record<string, unknown>) => string;
38
+ /**
39
+ * What this step contends over — a URL pattern, a storage key, the
40
+ * impersonation session. Two scenarios sharing a conflict key collide.
41
+ */
42
+ conflictKeys?: (params: Record<string, unknown>) => string[];
43
+ }
44
+ /**
45
+ * The v1 catalog.
46
+ *
47
+ * Action names and param shapes are the REAL ones the shipped adapters expose —
48
+ * verified against networkSyncAdapter (v5), storageSyncAdapter (v3),
49
+ * impersonateSyncAdapter (v1) and routeEventsSyncAdapter. Getting one wrong
50
+ * means a scenario that validates and then fails on a tester's device.
51
+ */
52
+ export declare const STEP_CATALOG: CatalogEntry[];
53
+ export declare function catalogEntry(tool: string, action: string): CatalogEntry | undefined;
54
+ /**
55
+ * The one humanizer. `params` must already be interpolated when the caller
56
+ * knows the variable values; unresolved `{{placeholders}}` simply render as
57
+ * themselves, which is what the pre-run preview wants.
58
+ */
59
+ export declare function describeStep(step: ScenarioStep, params?: unknown): string;
60
+ export declare function stepDurability(step: ScenarioStep): Durability;
61
+ /** True when the UI must show the "advanced" chip instead of a description. */
62
+ export declare function isAdvancedStep(step: ScenarioStep): boolean;
63
+ export declare function stepConflictKeys(step: ScenarioStep, params?: unknown): string[];
64
+ /**
65
+ * Do two conflict keys contend over the same thing?
66
+ *
67
+ * Storage keys and the impersonation session compare by equality, but URL
68
+ * patterns must not: `*pokeapi.co*` and `*pokeapi.co/api/v2/pokemon/pikachu*`
69
+ * are different strings that fight over the same traffic, and the override
70
+ * engine resolves that fight by first-match-wins in list order. Comparing
71
+ * strings would call that "no conflict" and let two scenarios silently produce
72
+ * a state neither of them describes — the exact lost afternoon this is here to
73
+ * prevent.
74
+ *
75
+ * The test: turn each pattern into a plausible concrete URL (wildcards become
76
+ * a filler chunk) and ask whether the OTHER pattern matches it. Overlapping
77
+ * globs match each other's probes; disjoint ones don't.
78
+ */
79
+ export declare function conflictKeysOverlap(a: string, b: string): boolean;
80
+ export declare const DURABILITY_LABEL: Record<Durability, string>;
81
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The active-scenario card.
3
+ *
4
+ * Lists every effect in plain English with its durability, and carries the
5
+ * three controls a tester reaches for in a regression loop: Deactivate,
6
+ * Copy repro info, Re-run.
7
+ *
8
+ * "Copy repro info" is the small feature that changes how bugs get filed. A
9
+ * ticket that carries the scenario, its variables, the build and the effect
10
+ * list can't be answered with "are you sure a scenario wasn't active?" — the
11
+ * answer is already in the ticket.
12
+ */
13
+ import type { ActiveScenario } from "../types";
14
+ export declare function reproBlock(active: ActiveScenario, build?: string): string;
15
+ export interface ActiveCardProps {
16
+ active: ActiveScenario;
17
+ build?: string;
18
+ busy?: boolean;
19
+ onDeactivate: () => void;
20
+ onRerun: () => void;
21
+ onCopied?: () => void;
22
+ }
23
+ export declare function ActiveCard({ active, build, busy, onDeactivate, onRerun, onCopied, }: ActiveCardProps): import("react").JSX.Element;
24
+ //# sourceMappingURL=ActiveCard.d.ts.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * What deactivation actually reversed — and what it could not.
3
+ *
4
+ * A "Deactivate" button implies clean. When it isn't clean, saying so here is
5
+ * the whole difference between a tool a tester trusts and one they quietly
6
+ * stop believing. Storage writes have no automatic inverse: we do not know
7
+ * what was there before, and guessing "delete it" would be a second wrong
8
+ * state on top of the first.
9
+ */
10
+ import type { DeactivateReport } from "../types";
11
+ export declare function DeactivateReportCard({ report, onDismiss, }: {
12
+ report: DeactivateReport;
13
+ onDismiss: () => void;
14
+ }): import("react").JSX.Element;
15
+ //# sourceMappingURL=DeactivateReportCard.d.ts.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * File a scenario under a flow.
3
+ *
4
+ * Existing folders are rows, not a text field with autocomplete, because the
5
+ * whole value of folders here is that a team converges on the SAME few names.
6
+ * Typing is available but it is the second option on the screen, under the
7
+ * list of what everyone else already uses.
8
+ *
9
+ * There is no "delete folder" and no "rename folder", deliberately: a folder
10
+ * is a string on a scenario (see ../folders), so it disappears when the last
11
+ * scenario leaves it, and a rename would silently fail to touch the read-only
12
+ * `code` scenarios sharing the name.
13
+ */
14
+ import type { Scenario } from "../types";
15
+ export interface FolderPickerScreenProps {
16
+ scenario: Scenario;
17
+ /** Every folder already in use on this device, sorted. */
18
+ folders: string[];
19
+ onPick: (folder: string | undefined) => void;
20
+ onBack: () => void;
21
+ }
22
+ export declare function FolderPickerScreen({ scenario, folders, onPick, onBack, }: FolderPickerScreenProps): import("react").JSX.Element;
23
+ //# sourceMappingURL=FolderPickerScreen.d.ts.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Review a recording, then save it.
3
+ *
4
+ * THE ONE IDEA THIS SCREEN EXISTS TO EXPRESS.
5
+ *
6
+ * There are two honest ways to reproduce "a bag with a Charizard in it":
7
+ *
8
+ * Replay the taps — open the store, pick the item, choose a size, add it.
9
+ * Slow, but it exercises the app the way a user does, and
10
+ * it stays true when the bag's internals change.
11
+ * Skip to result — write the bag. Instant, and it survives a redesign of
12
+ * the storefront, but it proves nothing about how the bag
13
+ * got filled.
14
+ *
15
+ * A tester needs both, usually in the same macro: skip to the full bag, then
16
+ * really drive the checkout they're actually testing. Every recorder that makes
17
+ * you choose up front is asking you to predict that split before you have seen
18
+ * the flow written down.
19
+ *
20
+ * So: record once, decide here. Both lanes were captured in the same session,
21
+ * so the decision is a tap — never a re-record — and the two can never describe
22
+ * different flows.
23
+ *
24
+ * THE CONTROL IS A LINE, NOT A SWITCH. There is exactly one decision on this
25
+ * screen — WHERE the setup ends and the test begins — so there is exactly one
26
+ * control: "Start here", on a row. Everything above the line is written
27
+ * instantly, everything below is really performed. An earlier version put a
28
+ * global replay/shortcut toggle in the header; it could not express the only
29
+ * arrangement anyone wants, and flipping it left the skipped-over taps still in
30
+ * the macro, typing searches whose results nobody opens.
31
+ *
32
+ * WHY THE DEFAULT IS TO PERFORM EVERYTHING: it is what actually happened. The
33
+ * shortcut is an optimization somebody opts into once they can see which steps
34
+ * it would swallow — the reverse default hands back a macro that silently skips
35
+ * the very interaction they meant to test.
36
+ */
37
+ import type { MacroStep } from "../record/types";
38
+ export interface MacroReviewScreenProps {
39
+ draft: MacroStep[];
40
+ onChange: (draft: MacroStep[]) => void;
41
+ onSave: (name: string, steps: MacroStep[]) => void;
42
+ onDiscard: () => void;
43
+ busy?: boolean;
44
+ }
45
+ export declare function MacroReviewScreen({ draft, onChange, onSave, onDiscard, busy, }: MacroReviewScreenProps): import("react").JSX.Element;
46
+ //# sourceMappingURL=MacroReviewScreen.d.ts.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The dry-run preview — "what this will do" before anything is done.
3
+ *
4
+ * This screen was the top ask from every seat we validated with, for the same
5
+ * reason stated three different ways: a tester will not run a black box thirty
6
+ * times a day, cannot write a bug report against one, and a developer who has
7
+ * to explain a failed run is back to being interrupted.
8
+ *
9
+ * It shows, in order: the variables (as real controls, never free text for a
10
+ * URL or a JSON blob), every step as a plain-English sentence with its
11
+ * durability, any conflict with what is already active, staleness, and the
12
+ * author's expected outcome. Pre-flight runs here, so unknown tools, missing
13
+ * variables and tier caps surface BEFORE step one — never after step three.
14
+ */
15
+ import type { Scenario, ScenarioVarValue } from "../types";
16
+ import type { PreflightResult } from "../engine";
17
+ export interface PreviewScreenProps {
18
+ scenario: Scenario;
19
+ initialValues: Record<string, ScenarioVarValue>;
20
+ /** Recomputed on every value change so conflicts and caps stay live. */
21
+ check: (values: Record<string, ScenarioVarValue>) => PreflightResult;
22
+ currentBuild?: string;
23
+ mode: "run" | "review-draft";
24
+ onRun: (values: Record<string, ScenarioVarValue>) => void;
25
+ onAcceptDraft?: () => void;
26
+ onDiscardDraft?: () => void;
27
+ onBack: () => void;
28
+ }
29
+ export declare function PreviewScreen({ scenario, initialValues, check, currentBuild, mode, onRun, onAcceptDraft, onDiscardDraft, onBack, }: PreviewScreenProps): import("react").JSX.Element;
30
+ //# sourceMappingURL=PreviewScreen.d.ts.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The recording banner — the whole UI of a recording in progress.
3
+ *
4
+ * Recording is the one Buoy mode where the tool has to be almost entirely
5
+ * absent: a tester is driving the real app, and every pixel of dev-tool chrome
6
+ * is a pixel of the flow they can't reach. So a recording gets one draggable
7
+ * pill: what it has counted so far, and Stop.
8
+ *
9
+ * Three things it is careful about:
10
+ *
11
+ * IT SHOWS THE COUNT, LIVE. A recorder with no feedback is a recorder you
12
+ * don't trust — the first question after ten taps is always "is it getting
13
+ * any of this?", and the answer has to be on screen, not on the next screen.
14
+ *
15
+ * IT COUNTS WHAT WAS RECORDED, NOT WHAT WAS TOUCHED. Taps on Buoy's own
16
+ * chrome, scrolls, and taps on unrecognisable elements never increment it, so
17
+ * a number that doesn't move is telling the truth about the last tap rather
18
+ * than lagging.
19
+ *
20
+ * IT IS DRAGGABLE, for the same reason the SIMULATED banner is: it will
21
+ * eventually sit exactly on top of the button somebody needs to press.
22
+ */
23
+ /** Height of the SIMULATED banner plus a gap, so the two never overlap. */
24
+ /**
25
+ * Below a standard 44pt navigation bar, not on it: at the top inset the banner
26
+ * covered the host app's back button and a tool sheet's window controls.
27
+ */
28
+ export declare const BANNER_TOP_GAP = 52;
29
+ export declare function RecordingBanner(): import("react").JSX.Element | null;
30
+ //# sourceMappingURL=RecordingBanner.d.ts.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Run progress and the run report.
3
+ *
4
+ * A failed run never shows a success state over a half-applied world. It stops
5
+ * on the failing step, prints the error verbatim, marks the rest skipped, and
6
+ * makes the choice explicit: undo what landed, or keep it deliberately. There
7
+ * is no silent default, because the silent default is what leaves a tester
8
+ * debugging state nobody can explain.
9
+ */
10
+ import type { RunState } from "../store/scenariosStore";
11
+ export interface RunProgressScreenProps {
12
+ runState: RunState;
13
+ busy?: boolean;
14
+ onUndoPartial: () => void;
15
+ onKeepPartial: () => void;
16
+ onRetry: () => void;
17
+ onDone: () => void;
18
+ onBack: () => void;
19
+ }
20
+ export declare function RunProgressScreen({ runState, busy, onUndoPartial, onKeepPartial, onRetry, onDone, onBack, }: RunProgressScreenProps): import("react").JSX.Element;
21
+ //# sourceMappingURL=RunProgressScreen.d.ts.map