@atmem/architect 0.2.1 → 0.2.2

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.
@@ -1,3 +1,5 @@
1
+ import {ReadyExampleChecklist} from './ReadyExample.jsx';
2
+ import {isReadyExample} from './ready-example.mjs';
1
3
  import {FileTree} from './FileTree.jsx';
2
4
  import {strFromU8} from 'fflate';
3
5
  import {CodePreview} from './CodePreview.jsx';
@@ -57,7 +59,8 @@ export function OutputsView({project,run,environment,session,onDownload,onBack,e
57
59
  return <section className="doc outputs-view" aria-label="Outputs">
58
60
  <header className="doc-head"><h2>Delivery</h2></header>
59
61
  <button className="link" onClick={onReview||onBack}>Back to Review</button>
60
- {session?.engineeringEnabled&&<ImplementationDelivery key={project.id} project={project} session={session} onBuild={onBuild} onSave={onSave} contextBusy={discussionBusy} designBundle={loading?null:bundle} onDesignDownload={onDownload} onDownloaded={onDownloaded} checkReceipt={checkReceipt} onExplore={setExploration} onNavigate={onNavigate}/>}
62
+ {isReadyExample(project)&&<ReadyExampleChecklist/>}
63
+ {session?.engineeringEnabled&&(isReadyExample(project)?<details><summary>Checks for your edited project</summary><ImplementationDelivery key={project.id} project={project} session={session} onBuild={onBuild} onSave={onSave} contextBusy={discussionBusy} designBundle={loading?null:bundle} onDesignDownload={onDownload} onDownloaded={onDownloaded} checkReceipt={checkReceipt} onExplore={setExploration} onNavigate={onNavigate}/></details>:<ImplementationDelivery key={project.id} project={project} session={session} onBuild={onBuild} onSave={onSave} contextBusy={discussionBusy} designBundle={loading?null:bundle} onDesignDownload={onDownload} onDownloaded={onDownloaded} checkReceipt={checkReceipt} onExplore={setExploration} onNavigate={onNavigate}/>)}
61
64
  <section aria-label="Project file explorer"><h3>Explore files</h3>
62
65
 
63
66
  {!session?.engineeringEnabled&&<><p>Design package · no implementation generated.</p><button className="btn" disabled={!bundle||loading||!!error||packing} onClick={downloadDesign}>{packing?'Preparing ZIP…':'Download project ZIP'}</button></>}
@@ -0,0 +1,16 @@
1
+ import React,{useState} from 'react';
2
+ import receipt from './reference-example/receipt.json';
3
+ export function ReadyExampleDownload({compact=false}){
4
+ const [busy,setBusy]=useState(false),[error,setError]=useState('');
5
+ async function download(){setBusy(true);setError('');try{
6
+ const response=await fetch(receipt.url);if(!response.ok)throw Error('Example download unavailable. Retry.');
7
+ const bytes=await response.arrayBuffer();const hash=[...new Uint8Array(await crypto.subtle.digest('SHA-256',bytes))].map(n=>n.toString(16).padStart(2,'0')).join('');
8
+ if(hash!==receipt.sha256)throw Error('Example integrity check failed. Retry.');
9
+ const url=URL.createObjectURL(new Blob([bytes],{type:'application/zip'})),link=document.createElement('a');link.href=url;link.download='atmem-ai-complete-example.zip';link.click();setTimeout(()=>URL.revokeObjectURL(url),60000);
10
+ }catch(e){setError(e.message)}finally{setBusy(false)}}
11
+ return <><button type="button" className={compact?'btn ready-download':'btn primary'} onClick={download} disabled={busy}>{busy?'Preparing…':compact?'Download example ZIP':'Download complete example'}</button>{error&&<p role="alert">{error}</p>}</>;
12
+ }
13
+ export function ReadyExampleChecklist(){
14
+ const rows=[['Requirements','Recorded'],['Cloud','AWS'],['Data country','Australia'],['Cloud region','Sydney'],['Cost worksheet','USD 7.56/month · sample'],['Implementation','Source + built runtime'],['Application checks','13 local tests passed'],['Terraform checks','Validated · 2 mock plans passed']];
15
+ return <section className="final-checklist ready-reference" aria-label="Prepared example checklist"><h3>Complete reference example</h3><p>These checks apply to the bundled example ZIP. Your edited project has separate checks.</p><ul>{rows.map(([name,value])=><li key={name}><span className="check-complete">✓</span><span>{name}</span><strong>{value}</strong></li>)}</ul><ReadyExampleDownload/><details><summary>Evidence and scope</summary><p>Local fixtures and mocked AWS. Live deployment and real tenant integration have not run.</p><p>Illustrative AWS usage; assumptions and official price snapshots are included.</p></details></section>;
16
+ }
package/src/main.jsx CHANGED
@@ -1,3 +1,5 @@
1
+ import {readyExample,isReadyExample} from './ready-example.mjs';
2
+ import {ReadyExampleDownload} from './ReadyExample.jsx';
1
3
  import {StarPrompt,STAR_URL} from './StarPrompt.jsx';
2
4
  import {prefillAcceptedSettings} from './engineering-defaults.mjs';
3
5
  import {LocationChoices} from './LocationChoices.jsx';
@@ -101,6 +103,7 @@ import {
101
103
  Rocket,
102
104
  BrainCircuit,
103
105
  Waypoints,
106
+ Volume2,
104
107
  Mic,
105
108
  Square,
106
109
  Eraser,
@@ -167,6 +170,7 @@ import { zipSync, strToU8 } from "fflate";
167
170
  // Each browser starts fresh; earlier projects are opened deliberately (⋯ → Open project file).
168
171
  const STORAGE = "atforma-workspace-v3";
169
172
  // A new project starts empty: the user describes what they want first.
173
+ import './onboarding.css';
170
174
  const emptyProject = () => ({ id: crypto.randomUUID(), version: 1, revision: 1, name: "New project", provider: "tools", requirements: "", nodes: [], edges: [] });
171
175
  const ALL_CATALOG = [...CATALOG, ...UPSTREAM_CATALOG];
172
176
  const KIND_ICON = {
@@ -646,6 +650,7 @@ function harnessSignals(was, now) {
646
650
  }
647
651
  const welcome = (project) => ({
648
652
  role: "assistant",
653
+ introduction: true,
649
654
  text: project.nodes.length || project.brief?.application?.trim()
650
655
  ? `Good to see you again — let's keep going.\n\n${opening(project)}`
651
656
  : `What would you like to build?`,
@@ -666,7 +671,8 @@ function readChatView(id){try{const value=JSON.parse(localStorage.getItem('atmem
666
671
  const pricingSignature=p=>JSON.stringify([p.id,p.estimate?.projectSize||'small',p.estimate?.usage,p.facts,p.decisions?.filter(d=>d.status==='Accepted'&&['cloud.region','data.country'].includes(d.subject)),[...storageNodes(p),...lambdaNodes(p),...googleNodes(p)].map(n=>[n.id,n.data.provider,n.data.service,n.data.config])]);
667
672
  function App() {
668
673
  const [starOffer,setStarOffer]=useState(0);
669
- const [project, setProject] = useState(readSaved);
674
+ const [introOpen,setIntroOpen]=useState(false),[voiceOpenRequest,setVoiceOpenRequest]=useState(0);
675
+ const [project, setProject] = useState(()=>new URL(location.href).searchParams.get('example')==='1'&&!projectIdFromUrl(location.href)?readyExample():readSaved());
670
676
  const projectRef = useRef(project);
671
677
  projectRef.current = project;
672
678
  const [initialCanvasView] = useState(()=>readCanvasView(project));
@@ -698,7 +704,7 @@ function App() {
698
704
  [history, setHistory] = useState([]),
699
705
  [future, setFuture] = useState([]),
700
706
  [toast, setToast] = useState(""),
701
- [modal, setModal] = useState(() => (readConnection() ? null : "welcome")),
707
+ [modal, setModal] = useState(null),
702
708
  [menu, setMenu] = useState(false),
703
709
  [palette, setPalette] = useState(false),
704
710
  [issuesOpen, setIssuesOpen] = useState(false),
@@ -782,6 +788,12 @@ function App() {
782
788
  const consultationAttempts = useRef(new Set());
783
789
  const agentAbort = useRef(null);
784
790
  const messageInput = useRef(null);
791
+ const hasIntroduction=messages[0]?.role==='assistant'&&(messages[0].introduction===true||messages[0].text==='What would you like to build?'||messages[0].text.startsWith('Good to see you again'));
792
+ const idleChat=hasIntroduction&&!introOpen&&architectureChatEntries(project,messages).length===1;
793
+ useEffect(()=>{
794
+ const resize=()=>{const field=messageInput.current;if(field){field.style.height='32px';field.style.height=Math.min(120,Math.max(32,field.scrollHeight))+'px';}};
795
+ resize();window.addEventListener('resize',resize);return()=>window.removeEventListener('resize',resize);
796
+ },[input,chatWidth,chatOpen]);
785
797
  const intakeRef=useRef(restoreQueuedMessages(messages,project.id));
786
798
  const [intake,setIntake]=useState(intakeRef.current),[intakePaused,setIntakePausedState]=useState(()=>restoreIntakePaused(messages)||intakeRef.current.length>0);
787
799
  const [intakeIssue,setIntakeIssue]=useState(null);
@@ -1667,6 +1679,8 @@ function App() {
1667
1679
  }
1668
1680
  }
1669
1681
  function switchTo(next) {
1682
+ setIntroOpen(false);setVoiceOpenRequest(0);
1683
+ const address=new URL(window.location.href);if(address.searchParams.get('project')!==next.id){address.searchParams.delete('project');address.searchParams.delete('example');window.history.replaceState(null,'',address);}
1670
1684
  chooseWorkspace('canvas');setBuildRequest(null);
1671
1685
  next = withDerived(next);
1672
1686
  const leaving = projectRef.current;
@@ -1725,8 +1739,14 @@ function App() {
1725
1739
  })();
1726
1740
  return () => { active = false; };
1727
1741
  }, [session?.token]);
1728
- function newProject() {
1729
- setModal("start-over");
1742
+ function newProject() { if(!busy&&!agentBusy&&!intake.length)setModal("start-over"); }
1743
+ function createOwnProject(){
1744
+ switchTo(validateImport(emptyProject()));setModal(connection?null:'connections');
1745
+ requestAnimationFrame(()=>document.querySelector('input[aria-label="Project name"]')?.focus());
1746
+ }
1747
+ function openReadyExample(){
1748
+ if(busy||agentBusy||intake.length)return;
1749
+ const example=readyExample();example.id=crypto.randomUUID();switchTo(example);setModal(null);
1730
1750
  }
1731
1751
  async function binProject(id) {
1732
1752
  try {
@@ -1840,7 +1860,7 @@ function App() {
1840
1860
  />
1841
1861
  );
1842
1862
  return (
1843
- <div style={{"--chat-panel-width":`${chatWidth}px`}} className={`app ${chatOpen&&!buildView ? "" : "chat-closed"} ${buildView?"build-workspace":""}`}>
1863
+ <div style={{"--chat-panel-width":`${chatWidth}px`}} className={`app ${idleChat?"idle-chat":""} ${chatOpen&&!buildView ? "" : "chat-closed"} ${buildView?"build-workspace":""}`}>
1844
1864
  <header className="bar">
1845
1865
  <button aria-label="About AtMem AI Architect" className="brand" onClick={() => setModal("about")} title="About AtMem AI Architect">
1846
1866
  <Mark />
@@ -1859,6 +1879,7 @@ function App() {
1859
1879
  /><PencilRuler size={14} aria-hidden="true"/></label>
1860
1880
  </nav>
1861
1881
  <div className="bar-actions">
1882
+ {isReadyExample(project)&&<><ReadyExampleDownload compact/><button className="btn" onClick={newProject} disabled={busy||agentBusy}>Create new project</button></>}
1862
1883
  {storageError && <span className="warn-text">Not saved</span>}
1863
1884
  <button
1864
1885
  className="icon"
@@ -1883,6 +1904,9 @@ function App() {
1883
1904
  {menu && (
1884
1905
  <div className="menu" role="menu" onClick={() => setMenu(false)}>
1885
1906
  <button role="menuitem" onClick={() => (refreshProjects(), setModal("projects"))} disabled={busy}><FolderOpen size={16}/>Projects</button>
1907
+ <a role="menuitem" href="https://atmem.ai" target="_blank" rel="noopener noreferrer">atmem.ai ↗</a>
1908
+ <a role="menuitem" href="mailto:hello@atmem.ai">Contact us: hello@atmem.ai</a>
1909
+ <button role="menuitem" onClick={openReadyExample} disabled={busy||agentBusy}><LayoutGrid size={16}/>Open complete AI example</button>
1886
1910
  <button role="menuitem" onClick={newProject} disabled={busy}><Plus size={16}/>New project</button>
1887
1911
  <hr/><button role="menuitem" onClick={() => setPalette(true)} disabled={busy}><Plus size={16}/>Add a block</button>
1888
1912
  <button role="menuitem" onClick={() => setModal("templates")} disabled={busy}><LayoutTemplate size={16}/>Templates</button>
@@ -1920,7 +1944,7 @@ function App() {
1920
1944
  {level!=="block"&&<aside className="board-components component-navigator" data-expanded={treeOpen} aria-label="Board components"><button className="btn" aria-label="Toggle components and layers" aria-expanded={treeOpen} onClick={()=>setTreeOpen(!treeOpen)}><LayoutGrid size={16}/> Components &amp; layers</button>
1921
1945
  {treeOpen&&<nav className="board-component-tree" aria-label="Component tree"><strong>{project.name}</strong><div className="tree-views"><button disabled={!blockReviews.length} onClick={()=>{reviewBlock((blockReviews.find(r=>r.status!=='reviewed')||blockReviews[0]).id);}}><CheckCheck size={16}/> Review blocks · {blockReviews.filter(r=>r.status==='reviewed').length}/{blockReviews.length}</button></div>{[...new Map(project.nodes.filter(n=>n.data.kind!=='note').map(n=>[layerOf(n).id,layerOf(n)])).values()].map(layer=><details open key={layer.id}><summary>{layer.label}</summary>{project.nodes.filter(n=>n.data.kind!=='note'&&layerOf(n).id===layer.id).map(n=><button key={n.id} aria-current={selected===n.id?'true':undefined} onClick={()=>{setMode('design');setDesignView('board');setFocus(null);setLevel('flow');setSelected(n.id);setTimeout(()=>fitView({nodes:[{id:n.id}],padding:1,maxZoom:1.2,duration:350}),100);}}><Database size={14}/><span>{n.data.label}<small>{n.data.service||n.data.provider}</small><small className={`tree-review-state ${blockReviews.find(r=>r.id===n.id)?.status}`}>{blockReviews.find(r=>r.id===n.id)?.statusLabel}</small></span></button>)}</details>)}</nav>}
1922
1946
  {treeOpen&&<button className="btn" disabled={busy} onClick={()=>setPalette(true)}><Plus size={16}/> Add component</button>}</aside>}
1923
- {!hasDesign&&!palette&&!dismissedStarts.includes(project.id)&&<section className="board-start" aria-label="Start your project"><button type="button" className="icon board-start-close" aria-label="Dismiss welcome card" title="Dismiss welcome card" onClick={dismissStart}><X size={16}/></button><h2>What would you like to create?</h2><div className="row"><button className="btn primary" onClick={()=>{dismissStart();setChatOpen(true);document.querySelector('.composer textarea, .chat textarea')?.focus();}}>Start chatting</button><button className="btn" onClick={()=>{setChatOpen(true);requirementsInput.current.click();}}>Upload requirements or diagram</button><button className="btn" onClick={()=>setModal("project-file")}>Open project</button></div><InlineNote label="Supported starting files">Word, PDF, text, Markdown, draw.io, images and Architect project JSON.</InlineNote></section>}
1947
+ {!hasDesign&&!palette&&!dismissedStarts.includes(project.id)&&<section className="board-start" aria-label="Start your project"><button type="button" className="icon board-start-close" aria-label="Dismiss welcome card" title="Dismiss welcome card" onClick={dismissStart}><X size={16}/></button><h2>What would you like to create?</h2><div className="row"><button className="btn primary" onClick={openReadyExample}>Explore complete AI example</button><button className="btn primary" onClick={()=>{dismissStart();setChatOpen(true);document.querySelector('.composer textarea, .chat textarea')?.focus();}}>Start chatting</button><button className="btn" onClick={()=>{setChatOpen(true);requirementsInput.current.click();}}>Upload requirements or diagram</button><button className="btn" onClick={()=>setModal("project-file")}>Open project</button></div><InlineNote label="Supported starting files">Word, PDF, text, Markdown, draw.io, images and Architect project JSON.</InlineNote></section>}
1924
1948
  <ReactFlow
1925
1949
  nodes={canvasNodes}
1926
1950
  edges={canvasEdges}
@@ -1986,8 +2010,8 @@ function App() {
1986
2010
  >
1987
2011
  <LayoutGrid size={17} />
1988
2012
  </button>
1989
- <button className="icon" title="Clear board" aria-label="Clear board" disabled={!project.nodes.length || busy} onClick={clearBoard}>
1990
- <Eraser size={17} />
2013
+ <button className="btn reset-board" title="Reset board and context" aria-label="Reset board and context" disabled={busy||agentBusy||intake.length>0} onClick={newProject}>
2014
+ <Trash2 size={17} /> Reset board
1991
2015
  </button>
1992
2016
  <button className="icon" title="Undo board change" aria-label="Undo" disabled={!history.length || busy || agentBusy} onClick={undo}>
1993
2017
  <Undo2 size={17} />
@@ -2033,8 +2057,8 @@ function App() {
2033
2057
  <Scan size={16}/> Select all
2034
2058
  </button>
2035
2059
  <div className="ctx-divider" role="separator"/>
2036
- <button role="menuitem" className="danger" disabled={!project.nodes.length} onClick={() => (clearBoard(), setCtx(null))}>
2037
- <Eraser size={16}/> Clear board
2060
+ <button role="menuitem" className="danger" disabled={!project.nodes.length} onClick={() => (newProject(), setCtx(null))}>
2061
+ <Trash2 size={16}/> Reset board and context
2038
2062
  </button>
2039
2063
  </>
2040
2064
  )}
@@ -2199,7 +2223,7 @@ function App() {
2199
2223
 
2200
2224
  {!chatOpen&&!buildView && <button className="icon chat-reopen" title="Show chat" aria-label="Show chat" onClick={()=>setChatOpen(true)}><PanelRightOpen size={18}/></button>}
2201
2225
  {chatOpen&&!buildView && (
2202
- <aside className="chat" aria-label="Chat"><ChatResizeHandle width={chatWidth} onChange={resizeChat}/>
2226
+ <aside className={`chat ${idleChat?"idle":""}`} aria-label="Chat"><ChatResizeHandle width={chatWidth} onChange={resizeChat}/>
2203
2227
  <div className="chat-head">
2204
2228
  <div className="chat-heading-row"><strong className="chat-title" aria-label="AI Architect">
2205
2229
  <Mark size={24} />
@@ -2221,6 +2245,10 @@ function App() {
2221
2245
  <span className={`led ${connection && session ? "on" : ""}`} />
2222
2246
  {connection ? `${connection.model}${connection.effort ? ` · ${EFFORT_LABEL[connection.effort] || connection.effort}` : ""}` : "Connect a model"}
2223
2247
  </button>
2248
+ <span className="chat-top-utilities">
2249
+ {hasIntroduction&&<button type="button" aria-expanded={introOpen} onClick={()=>setIntroOpen(value=>!value)}>{introOpen?'▾':'▸'} Introduction</button>}
2250
+ {session&&<button type="button" className="icon" aria-label="Voice settings" title="Voice settings" onClick={()=>setVoiceOpenRequest(value=>value+1)}><Volume2 size={15}/></button>}
2251
+ </span>
2224
2252
  </div>
2225
2253
  <div className="skills" aria-label="Active skills">
2226
2254
  <button className="known-bar" disabled={!session} aria-label="AtMem memory" title="Open AtMem memory, confirmed decisions and sync activity" onClick={()=>setModal("memory")}><BrainActivity size={18} active={false}/>{memoryLabel}<ChevronRight size={14}/></button>
@@ -2270,6 +2298,7 @@ function App() {
2270
2298
  if(entry.kind==='activity-error')return <p key="activity-error" role="alert">{entry.error}</p>;
2271
2299
  if(entry.kind==='legacy-activity')return <EarlierArchitectureActivity key={entry.id} entries={entry.entries} projectId={project.id} session={session}/>;
2272
2300
  if(entry.kind==='activity')return <ArchitectureTurn key={project.id+':'+entry.turn.id} turn={entry.turn} number={entry.number} projectId={project.id} session={session} active={agentBusy&&entry.turn.clientStatus==='submitted'&&entry.turn.id===latestArchitectureTurn?.id} working={agentBusy&&entry.turn.clientStatus==='submitted'} onStop={()=>agentAbort.current?.abort()}/>;
2301
+ if(entry.index===0&&hasIntroduction&&!introOpen)return null;
2273
2302
  const m=entry.message,i=entry.index,turn=project.extensions?.architectureActivity?.turns?.find(t=>t.id===m.activityId),messageTime=m.createdAt||(m.role==='assistant'?turn?.completedAt:null)||turn?.startedAt;
2274
2303
  return (
2275
2304
  <div key={i} className={`msg ${m.role}`}>
@@ -2376,7 +2405,7 @@ function App() {
2376
2405
  </button>
2377
2406
  </div>
2378
2407
  </form>
2379
- {session&&<OpenAIVoicePanel key={project.id} session={session} connection={connection} transportRef={openaiVoice} onConnections={()=>setModal('connections')} onStart={()=>{voice.current?.cancel();setVoiceError('');}} onTranscript={(text,automatic)=>{
2408
+ {session&&<OpenAIVoicePanel hideTrigger openRequest={voiceOpenRequest} key={project.id} session={session} connection={connection} transportRef={openaiVoice} onConnections={()=>setModal('connections')} onStart={()=>{voice.current?.cancel();setVoiceError('');}} onTranscript={(text,automatic)=>{
2380
2409
  if(automatic&&!input.trim()&&!attachments.length){setInput(text);void send(text);}
2381
2410
  else setInput(current=>current+(current.trim()?' ':'')+text);
2382
2411
  }}/>}
@@ -2429,22 +2458,20 @@ function App() {
2429
2458
  /></div>
2430
2459
  ) : modal === "start-over" ? (
2431
2460
  <>
2432
- <h2>Start over?</h2>
2433
- <p>Start a new idea with an empty board and chat. Requirements, decisions and estimates start fresh too. Your model connection stays.</p>
2461
+ <h2>Reset board and context?</h2>
2462
+ <p>Start fresh with an empty board, chat, requirements and decisions. Your saved projects and model connection stay.</p>
2434
2463
  <div className="reset-actions">
2435
2464
  <button className="btn primary" title="Download your project, then start a new one" aria-label="Save project and start over" onClick={() => {
2436
2465
  if (!saveProjectFile()) return;
2437
- switchTo(validateImport(emptyProject()));
2438
- setModal(null);
2466
+ createOwnProject();
2439
2467
  }}><Package size={18}/> Save & start new</button>
2440
2468
  <button
2441
- className="btn" title="Start a new project without downloading this one" aria-label="Start over without download"
2469
+ className="btn" title="Start a new project without downloading this one" aria-label="Confirm reset board and context"
2442
2470
  onClick={() => {
2443
- switchTo(validateImport(emptyProject()));
2444
- setModal(null);
2471
+ createOwnProject();
2445
2472
  }}
2446
2473
  >
2447
- <Plus size={18}/> Start without saving
2474
+ <Trash2 size={18}/> Reset board and context
2448
2475
  </button>
2449
2476
  <button className="btn ghost" onClick={() => setModal(null)}>
2450
2477
  <ArrowLeft size={18}/> Keep working
@@ -0,0 +1,10 @@
1
+ @media(max-width:900px){html,body,#root{height:100%;overflow:hidden}.app{height:100dvh;min-height:0;grid-template-columns:minmax(0,1fr);grid-template-rows:auto auto minmax(100px,1fr) minmax(300px,50dvh);overflow:hidden}.app.chat-closed,.app.build-workspace{grid-template-rows:auto auto minmax(0,1fr)}.app>.canvas{min-height:0;height:auto;overflow:hidden}.app>.chat{height:auto;min-height:0;overflow:hidden}.chat>.messages{min-height:0;flex:1;overflow:auto}.chat>.composer{position:relative;flex-shrink:0;background:var(--paper)}}
2
+ .chat>.composer{padding:6px 8px;border-radius:14px}.chat>.composer textarea{min-height:32px;height:32px;max-height:120px;padding:4px 6px;font-size:14px;line-height:20px;resize:none;overflow-y:auto}.chat>.composer .composer-actions{padding-top:2px}.chat>.composer .composer-actions button{width:32px;height:32px;min-height:32px;padding:6px}
3
+ .chat>.chat-head{display:flex;flex-direction:row;align-items:center;justify-content:flex-start;flex-wrap:wrap;gap:4px 6px;padding:4px 8px;min-height:0}.chat-head>.chat-heading-row{display:contents}.chat-head .chat-title{display:none}.chat-head>.model{order:0;max-width:100%;font-size:12px;line-height:18px;min-height:26px;padding:3px 7px;margin:0}.chat-head>.live-toggle{order:1;flex:0 0 auto;width:auto;display:inline-flex;align-items:center;gap:4px;white-space:nowrap;font-size:11px;line-height:18px;padding:0;margin:0}.chat-head .live-toggle input{width:13px;height:13px;margin:0}.chat-head .icon{order:2;width:26px;height:26px;min-height:26px;padding:4px;margin:0}.chat-head .icon svg{width:15px;height:15px}.chat>.skills{display:flex;align-items:center;flex-wrap:nowrap;overflow-x:auto;gap:3px;padding:2px 8px 4px;margin:0;min-height:0;background:transparent;border-radius:0}.chat>.skills .known-bar,.chat>.skills .skill{flex:0 0 auto;width:auto;font-size:11px;line-height:16px;min-height:24px;padding:3px 6px;margin:0;gap:4px;border-radius:7px}.chat>.skills svg{width:13px;height:13px}
4
+ .chat-head .chat-top-utilities{display:inline-flex;align-items:center;gap:4px;order:3;margin-left:auto}
5
+ .chat-head .chat-top-utilities button{font-size:11px;line-height:18px;min-height:26px;padding:3px 6px;border:0;background:transparent;color:var(--ink-2)}
6
+ .chat>.voice-status:empty{display:none}
7
+ .chat.idle>.messages{display:none}
8
+ @media(max-width:900px){.app.idle-chat{grid-template-rows:auto auto minmax(100px,1fr) auto}.app>.chat.idle{height:auto;min-height:0}}
9
+ .ready-reference{margin:20px 0}.ready-reference>p{margin:8px 0}.ready-reference li{display:flex;align-items:center;gap:10px;padding:8px 0}.ready-reference li>span:nth-child(2){flex:1}.ready-reference details{margin-top:10px}.ready-reference details p{margin:8px 0}
10
+ .bar-actions .ready-download{font-size:12px;padding:6px 10px}.toolbar .reset-board{font-size:12px;padding:6px 9px;gap:5px}
@@ -0,0 +1,5 @@
1
+ import file from './reference-example/project.json' with {type:'json'};
2
+ import {fromProjectFile} from './project-file.mjs';
3
+ export const READY_EXAMPLE_ID='atmem-ai-ready-example-v1';
4
+ export function readyExample(){const {project}=fromProjectFile(structuredClone(file));return project;}
5
+ export function isReadyExample(project){return project?.extensions?.preparedExample?.version===1&&project?.extensions?.preparedExample?.owner==='AtMem example author';}