Skip to content

docs: add GraphComplete architecture synthesis prototype - #352

Open
vishaltandale00 wants to merge 2 commits into
mainfrom
codex/graphcomplete-architecture-synthesis
Open

docs: add GraphComplete architecture synthesis prototype#352
vishaltandale00 wants to merge 2 commits into
mainfrom
codex/graphcomplete-architecture-synthesis

Conversation

@vishaltandale00

Copy link
Copy Markdown
Owner

Summary

  • add a throwaway interactive architecture prototype with URL-switchable variants
  • preserve a dense, persistent graph while exposing the Build n4 node-detail contract
  • show source node -> invoke -> child inputGraph -> complete -> response graph provenance
  • anchor complete symbols directly on their connecting edges
  • distinguish click-to-inspect selection from the current response status

Validation

  • visual screenshot inspection of the final synthesis figure
  • all 9 complete connectors geometrically verified on their Bezier edges
  • no viewport overflow or browser console warnings
  • npm run build passes
  • npm run check reaches 1,046 passing tests but remains red on test/evidence-capture-integrity.test.mjs: expected child status 0, received status 9
  • the same evidence-capture test also fails in isolation with status 9

vishaltandale00 and others added 2 commits August 29, 2026 17:22
Claude Code loads CLAUDE.md, not AGENTS.md, so the repo agent
instructions were not reaching Claude sessions. Import instead of
duplicating so AGENTS.md stays the single source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T19:07:04.443102Z cf125f6 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cf125f6d6d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

function layerShape(id,x,y,selected=false){const g=S("g",{class:`layer layer-${id}`,transform:`translate(${x} ${y})`,"data-id":id});g.append(S("rect",{class:"shell",x:0,y:0,width:106,height:64,rx:19}));g.append(S("text",{class:"layer-label",x:53,y:14,fill:id==="build"?colors.purple:colors.green},id.toUpperCase()));g.append(S("path",{d:"M53 31 L28 46 M53 31 V45 M53 31 L78 46",fill:"none",stroke:colors.line,"stroke-width":1.4}));[[53,27],[26,50],[53,50],[80,50]].forEach(([cx,cy],i)=>g.append(S("circle",{class:`node ${i===0?"root":""} ${selected&&i===3?"selected":""}`,cx,cy,r:i===0?6:5,"data-node-index":i})));return g}
function completeGlyph(x,y,angle=0,extra=""){const g=S("g",{class:`complete ${extra}`,transform:`translate(${x} ${y}) rotate(${angle})`});g.append(S("rect",{x:-24,y:-9,width:48,height:18,rx:9,fill:"#fbfaf6"}),S("path",{d:"M-22 0 H22",stroke:colors.orange,"stroke-width":2}),S("path",{d:"M-14 -6 L-8 0 L-14 6 L-20 0 Z",fill:"#f8e9de",stroke:colors.orange}),S("circle",{r:4.5,fill:colors.orange}),S("path",{d:"M14 -6 L20 0 L14 6 L8 0 Z",fill:"#f8e9de",stroke:colors.orange}));return g}
function buildGraph(svg,layoutName){const config=layouts[layoutName],map={};svg.append(defs(),S("rect",{x:18,y:18,width:1164,height:684,rx:30,fill:"#fbfaf6",stroke:"#c5cad1","stroke-width":2}));config.layers.forEach(([id,x,y])=>map[id]={id,x,y,cx:x+53,cy:y+32});const edgeGroup=S("g"),completeGroup=S("g"),layerGroup=S("g"),actionGroup=S("g"),childGroup=S("g",{class:"invoke-chain"}),pointer=S("g",{class:"pointer",transform:`translate(${config.center[0]} ${config.center[1]-112})`});
commonEdges.filter(([a,b])=>map[a]&&map[b]).forEach(([a,b],i)=>{const A=map[a],B=map[b],mx=(A.cx+B.cx)/2,my=(A.cy+B.cy)/2,qx=mx-10,qy=my+12,px=.25*A.cx+.5*qx+.25*B.cx,py=.25*A.cy+.5*qy+.25*B.cy,angle=Math.atan2(B.cy-A.cy,B.cx-A.cx)*180/Math.PI,edge=S("path",{class:`edge ${i%4===0?"soft":""}`,d:`M${A.cx} ${A.cy} Q${qx} ${qy} ${B.cx} ${B.cy}`,"data-edge-index":i});edgeGroup.append(edge);if(i%3===0){const glyph=completeGlyph(px,py,angle);glyph.setAttribute("data-edge-index",i);glyph.setAttribute("data-anchor",`${px},${py}`);completeGroup.append(glyph)}});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reserve complete markers for interaction completion

This loop adds a completeGlyph to every third ordinary commonEdges entry, so the architecture view depicts multiple complete operations between layers such as Contract and Context. Completion actually occurs at the interaction-level complete(inputGraph)/graph.submit(interactionNode) boundary and is not an ordinary graph-edge relation; using these markers throughout the graph teaches the wrong execution contract.

AGENTS.md reference: AGENTS.md:L7-L9

Useful? React with 👍 / 👎.

pointer.append(S("path",{class:"current-link",d:"M49 0 H62",fill:"none"}),S("rect",{x:-49,y:-10,width:98,height:20,rx:10}),S("text",{x:0,y:3},"CURRENT · I₀"));
const goals=S("g");config.goals.forEach(([label,x,y,t])=>{const c=t==="orange"?colors.orange:t==="purple"?colors.purple:colors.green,g=S("g",{class:"goal",transform:`translate(${x} ${y})`});g.append(S("rect",{width:112,height:28,rx:12,fill:t==="orange"?"#f8e9de":t==="purple"?"#eee8f5":"#e5f1eb",stroke:c}),S("text",{x:56,y:18,fill:c},label));goals.append(g)});
svg.append(edgeGroup,completeGroup,layerGroup,input,actionGroup,childGroup,pointer,goals);svg._graph={config,map,pointer,childGroup,selected:"build",stage:0};}
function nodeStory(id,index){const stories=mockNodes[id]||["Inspect graph work","Trace its inputs","Review its evidence","Choose its next action"];return{copy:stories[index]||stories[0],state:index===0?"accepted":index===1?"current work":index===2?"evidence attached":"action available",input:mockInputs[id]||"prior visible layers",action:invokeLayers.has(id)&&index>=2?"invoke → inputGraph":"expand → response layer"}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Report GraphComplete's actual record states

When a user inspects nodes n2–n4, the state field reports index-derived labels such as current work, evidence attached, and action available, even though GraphComplete's explicit record states are draft, accepted, and stopped. Because the tooltip labels this value as state and mixes these labels with accepted, the prototype obscures the state contract it is intended to explain; keep status/role metadata separate from the real record state.

AGENTS.md reference: AGENTS.md:L9-L9

Useful? React with 👍 / 👎.

function hideNodeTooltip(svg){const id=svg._graph?.pinnedLayer;if(id){const g=svg.querySelector(`.layer-${id}`);if(g)showLayerInspector(svg,id,g)}else svg.closest(".frame").querySelector(".tooltip")?.classList.remove("open")}
function selectNode(svg,g,event){svg.querySelectorAll(".layer").forEach(x=>x.classList.remove("selected"));g.classList.add("selected");svg._graph.selected=g.dataset.id;if(svg.closest(".variant-a")){const lens=svg.closest(".frame").querySelector(".lens"),frame=svg.closest(".frame").getBoundingClientRect(),box=g.getBoundingClientRect();lens.querySelector("h2").textContent=g.dataset.id[0].toUpperCase()+g.dataset.id.slice(1);lens.style.left=`${Math.min(frame.width-255,Math.max(14,box.left-frame.left+box.width*.65))}px`;lens.style.top=`${Math.min(frame.height-235,Math.max(58,box.top-frame.top-118))}px`;lens.classList.add("open")}if(svg.closest(".variant-c,.interactive-graph")){svg._graph.pinnedLayer=g.dataset.id;showLayerInspector(svg,g.dataset.id,g)}}
function setStage(svg,stage){svg._graph.stage=stage;svg.classList.remove("stage-0","stage-1","stage-2","stage-3","stage-4");svg.classList.add(`stage-${stage}`);svg.querySelectorAll(".layer").forEach(x=>x.classList.toggle("selected",stage>=1&&x.dataset.id==="build"));svg.querySelectorAll(".invoke-complete").forEach(x=>x.classList.toggle("hot",stage>=3));const p=svg._graph.pointer,label=p.querySelector("text");if(stage===4){const build=svg._graph.map.build,x=Math.min(1110,build.cx+150),responseY=Math.min(620,build.cy+265);p.setAttribute("transform",`translate(${x-115} ${responseY})`);label.textContent="CURRENT RESPONSE"}else{const [cx,cy]=svg._graph.config.center;p.setAttribute("transform",`translate(${cx} ${cy-112})`);label.textContent="CURRENT · I₀"}}
function runAction(svg,action){if(action==="expand"){svg.classList.add("dim-actions");setTimeout(()=>svg.classList.remove("dim-actions"),650)}else if(action==="invoke"){setStage(svg,2);setTimeout(()=>setStage(svg,3),550);setTimeout(()=>setStage(svg,4),1100)}else if(action==="complete"){setStage(svg,3)}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reveal the response at the Complete stage

Selecting stage 04 or clicking any Show/Resolve complete button calls setStage(svg, 3), but .child-response becomes visible only in stage 4. Consequently the step whose copy says the accepted response graph is attached still hides that graph, and clicking Complete in the initially completed D–G variants makes an already visible response disappear; stage 3 should expose the accepted response while stage 4 only advances the pointer.

Useful? React with 👍 / 👎.

function showNodeTooltip(svg,id,index,node){const tip=svg.closest(".frame").querySelector(".tooltip"),frame=svg.closest(".frame").getBoundingClientRect(),box=node.getBoundingClientRect(),story=nodeStory(id,index);tip.classList.remove("layer-view");tip.querySelector("strong").textContent=`${id} · node n${index+1}`;tip.querySelector(".tooltip-copy").textContent=story.copy;tip.querySelector(".tooltip-state").textContent=story.state;tip.querySelector(".tooltip-input").textContent=story.input;tip.querySelector(".tooltip-action").textContent=story.action;positionTooltip(tip,frame,box,230,145);tip.classList.add("open")}
function showLayerInspector(svg,id,g){const tip=svg.closest(".frame").querySelector(".tooltip"),frame=svg.closest(".frame").getBoundingClientRect(),box=g.getBoundingClientRect(),stories=mockNodes[id]||[];tip.classList.add("layer-view");tip.querySelector("strong").textContent=`${id} layer · ${stories.length} nodes`;tip.querySelector(".tooltip-copy").textContent=layerPurposes[id]||"A bounded layer of connected graph work.";const list=tip.querySelector(".tooltip-node-list");list.innerHTML=stories.map((copy,index)=>`<div class="tooltip-node-row"><b>n${index+1}</b><span>${copy}</span><em>${index===0?"accepted":index===1?"work":index===2?"evidence":"action"}</em></div>`).join("");positionTooltip(tip,frame,box,280,245);tip.classList.add("open")}
function hideNodeTooltip(svg){const id=svg._graph?.pinnedLayer;if(id){const g=svg.querySelector(`.layer-${id}`);if(g)showLayerInspector(svg,id,g)}else svg.closest(".frame").querySelector(".tooltip")?.classList.remove("open")}
function selectNode(svg,g,event){svg.querySelectorAll(".layer").forEach(x=>x.classList.remove("selected"));g.classList.add("selected");svg._graph.selected=g.dataset.id;if(svg.closest(".variant-a")){const lens=svg.closest(".frame").querySelector(".lens"),frame=svg.closest(".frame").getBoundingClientRect(),box=g.getBoundingClientRect();lens.querySelector("h2").textContent=g.dataset.id[0].toUpperCase()+g.dataset.id.slice(1);lens.style.left=`${Math.min(frame.width-255,Math.max(14,box.left-frame.left+box.width*.65))}px`;lens.style.top=`${Math.min(frame.height-235,Math.max(58,box.top-frame.top-118))}px`;lens.classList.add("open")}if(svg.closest(".variant-c,.interactive-graph")){svg._graph.pinnedLayer=g.dataset.id;showLayerInspector(svg,g.dataset.id,g)}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Refresh all lens fields for the selected layer

In variant A, clicking a non-Build layer updates only the lens heading. The eyebrow, description, input, context, and actions remain the hard-coded Build n2 contract, so selecting Contract, Research, or another layer produces a visibly mismatched detail lens; populate the entire lens from the selected layer/node or restrict selection to the contract it actually describes.

Useful? React with 👍 / 👎.

.page{height:100%;display:grid;grid-template-rows:auto minmax(0,1fr);padding:18px 20px 72px;gap:13px}
header{display:grid;grid-template-columns:230px minmax(0,1fr) auto;align-items:end;gap:28px}.brand small{display:block;margin-bottom:5px;color:var(--orange);font-size:9px;font-weight:900;letter-spacing:.16em;text-transform:uppercase}.brand strong,.copy h1{font-family:Georgia,"Times New Roman",serif;font-weight:500}.brand strong{font-size:22px}.copy h1{margin:0 0 4px;font-size:clamp(22px,3vw,34px);letter-spacing:-.025em}.copy p{margin:0;color:var(--muted);font-size:11px}.prototype-id{padding:7px 10px;border:1px solid #c8cdd3;border-radius:999px;background:rgba(255,253,248,.7);font-size:9px;font-weight:850;letter-spacing:.08em;text-transform:uppercase}
.variant{display:none;min-height:0}.variant.active{display:block}.frame{height:100%;min-height:0;position:relative;overflow:hidden;border:1px solid #bfc5cd;border-radius:26px;background:#fbfaf6;box-shadow:var(--shadow)}
svg.graph{display:block;width:100%;height:100%;touch-action:none}.edge{fill:none;stroke:var(--line);stroke-width:2;marker-end:url(#gray);transition:opacity .25s,stroke .25s}.edge.soft{stroke-dasharray:5 6;opacity:.7}.layer{cursor:pointer;transition:opacity .2s}.layer .shell{fill:var(--surface);stroke:#8792a0;stroke-width:1.8;transition:stroke .2s,stroke-width .2s,filter .2s}.layer:hover .shell,.layer.selected .shell{stroke:var(--purple);stroke-width:3;filter:url(#shadow)}.node{fill:#f6f6f3;stroke:#8d98a5;stroke-width:1.4}.node.root{fill:var(--blue-soft);stroke:var(--blue)}.node.selected{fill:var(--purple-soft);stroke:var(--purple);stroke-width:2.5}.layer-label{font-size:8px;font-weight:850;letter-spacing:.7px;text-anchor:middle}.complete{opacity:.76;transition:opacity .2s,filter .2s}.complete.hot{opacity:1;filter:drop-shadow(0 0 6px rgba(180,92,45,.6));animation:pulse .8s ease-in-out infinite alternate}.expand-arrow{fill:none;stroke:var(--blue);stroke-width:3.5;marker-end:url(#blue);stroke-linecap:round}.invoke-arrow{fill:none;stroke:var(--purple);stroke-width:3.5;marker-end:url(#purple);stroke-linecap:round}.input-ring{fill:var(--surface);stroke:var(--purple);stroke-width:3}.input-title{font-size:10px;font-weight:900;fill:var(--purple);text-anchor:middle;letter-spacing:.7px}.pointer rect{fill:var(--purple-soft);stroke:var(--purple);stroke-width:1.4}.pointer text{font-size:7px;font-weight:900;fill:var(--purple);text-anchor:middle;letter-spacing:.25px}.pointer .current-link{opacity:0;stroke:var(--purple);stroke-width:1.5}.stage-4 .pointer .current-link{opacity:1}.goal rect{stroke-width:1.5}.goal text{font-size:8px;font-weight:850;text-anchor:middle}.invoke-chain,.child-response{opacity:0;transition:opacity .3s}.stage-2 .invoke-chain,.stage-3 .invoke-chain,.stage-4 .invoke-chain{opacity:1}.stage-3 .invoke-complete,.stage-4 .invoke-complete{opacity:1;filter:drop-shadow(0 0 7px rgba(180,92,45,.7))}.stage-4 .child-response{opacity:1}.graph.dim-continuity .edge,.graph.dim-continuity .layer{opacity:.15}.graph.dim-complete .complete,.graph.dim-complete .layer{opacity:.15}.graph.dim-actions .edge,.graph.dim-actions .complete,.graph.dim-actions .layer{opacity:.15}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the selected legend category visible

When a user clicks the complete or continuity legend entries in variants C–G, the handler applies dim-complete or dim-continuity, but these selectors reduce the opacity of the very .complete or .edge elements being selected. The interaction therefore hides the category it promises to emphasize; dim the other categories instead, as the dim-actions treatment already does.

Useful? React with 👍 / 👎.

.page{height:100%;display:grid;grid-template-rows:auto minmax(0,1fr);padding:18px 20px 72px;gap:13px}
header{display:grid;grid-template-columns:230px minmax(0,1fr) auto;align-items:end;gap:28px}.brand small{display:block;margin-bottom:5px;color:var(--orange);font-size:9px;font-weight:900;letter-spacing:.16em;text-transform:uppercase}.brand strong,.copy h1{font-family:Georgia,"Times New Roman",serif;font-weight:500}.brand strong{font-size:22px}.copy h1{margin:0 0 4px;font-size:clamp(22px,3vw,34px);letter-spacing:-.025em}.copy p{margin:0;color:var(--muted);font-size:11px}.prototype-id{padding:7px 10px;border:1px solid #c8cdd3;border-radius:999px;background:rgba(255,253,248,.7);font-size:9px;font-weight:850;letter-spacing:.08em;text-transform:uppercase}
.variant{display:none;min-height:0}.variant.active{display:block}.frame{height:100%;min-height:0;position:relative;overflow:hidden;border:1px solid #bfc5cd;border-radius:26px;background:#fbfaf6;box-shadow:var(--shadow)}
svg.graph{display:block;width:100%;height:100%;touch-action:none}.edge{fill:none;stroke:var(--line);stroke-width:2;marker-end:url(#gray);transition:opacity .25s,stroke .25s}.edge.soft{stroke-dasharray:5 6;opacity:.7}.layer{cursor:pointer;transition:opacity .2s}.layer .shell{fill:var(--surface);stroke:#8792a0;stroke-width:1.8;transition:stroke .2s,stroke-width .2s,filter .2s}.layer:hover .shell,.layer.selected .shell{stroke:var(--purple);stroke-width:3;filter:url(#shadow)}.node{fill:#f6f6f3;stroke:#8d98a5;stroke-width:1.4}.node.root{fill:var(--blue-soft);stroke:var(--blue)}.node.selected{fill:var(--purple-soft);stroke:var(--purple);stroke-width:2.5}.layer-label{font-size:8px;font-weight:850;letter-spacing:.7px;text-anchor:middle}.complete{opacity:.76;transition:opacity .2s,filter .2s}.complete.hot{opacity:1;filter:drop-shadow(0 0 6px rgba(180,92,45,.6));animation:pulse .8s ease-in-out infinite alternate}.expand-arrow{fill:none;stroke:var(--blue);stroke-width:3.5;marker-end:url(#blue);stroke-linecap:round}.invoke-arrow{fill:none;stroke:var(--purple);stroke-width:3.5;marker-end:url(#purple);stroke-linecap:round}.input-ring{fill:var(--surface);stroke:var(--purple);stroke-width:3}.input-title{font-size:10px;font-weight:900;fill:var(--purple);text-anchor:middle;letter-spacing:.7px}.pointer rect{fill:var(--purple-soft);stroke:var(--purple);stroke-width:1.4}.pointer text{font-size:7px;font-weight:900;fill:var(--purple);text-anchor:middle;letter-spacing:.25px}.pointer .current-link{opacity:0;stroke:var(--purple);stroke-width:1.5}.stage-4 .pointer .current-link{opacity:1}.goal rect{stroke-width:1.5}.goal text{font-size:8px;font-weight:850;text-anchor:middle}.invoke-chain,.child-response{opacity:0;transition:opacity .3s}.stage-2 .invoke-chain,.stage-3 .invoke-chain,.stage-4 .invoke-chain{opacity:1}.stage-3 .invoke-complete,.stage-4 .invoke-complete{opacity:1;filter:drop-shadow(0 0 7px rgba(180,92,45,.7))}.stage-4 .child-response{opacity:1}.graph.dim-continuity .edge,.graph.dim-continuity .layer{opacity:.15}.graph.dim-complete .complete,.graph.dim-complete .layer{opacity:.15}.graph.dim-actions .edge,.graph.dim-actions .complete,.graph.dim-actions .layer{opacity:.15}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Render ordinary graph edges as undirected

Every gray .edge receives marker-end:url(#gray), so the continuity network is rendered as a directed graph. The repository's GraphEdge contract stores an unordered endpoint pair and the README explicitly defines these edges as undirected; direction belongs to node-owned navigate or invoke actions. Readers of this architecture prototype will otherwise infer ordering and authority that the graph does not contain.

Useful? React with 👍 / 👎.


<section class="variant variant-a" data-variant="A" data-name="Anchored lens">
<div class="frame">
<div class="grammar"><span><i class="swatch blue"></i>expand</span><span><i class="swatch purple"></i>invoke</span><span><i class="swatch orange"></i>complete</span></div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include reference in the action grammar

The legends present expand, invoke, and complete as the graph grammar but omit the other supported navigate relation, reference. Because the dense diagram includes shared and cyclic supporting context—the use case references are designed for—readers will interpret all navigation as expansion and miss the contract that expansion must be acyclic while references may revisit accepted context; add a distinct reference treatment and identify the relevant links.

Useful? React with 👍 / 👎.

function hideNodeTooltip(svg){const id=svg._graph?.pinnedLayer;if(id){const g=svg.querySelector(`.layer-${id}`);if(g)showLayerInspector(svg,id,g)}else svg.closest(".frame").querySelector(".tooltip")?.classList.remove("open")}
function selectNode(svg,g,event){svg.querySelectorAll(".layer").forEach(x=>x.classList.remove("selected"));g.classList.add("selected");svg._graph.selected=g.dataset.id;if(svg.closest(".variant-a")){const lens=svg.closest(".frame").querySelector(".lens"),frame=svg.closest(".frame").getBoundingClientRect(),box=g.getBoundingClientRect();lens.querySelector("h2").textContent=g.dataset.id[0].toUpperCase()+g.dataset.id.slice(1);lens.style.left=`${Math.min(frame.width-255,Math.max(14,box.left-frame.left+box.width*.65))}px`;lens.style.top=`${Math.min(frame.height-235,Math.max(58,box.top-frame.top-118))}px`;lens.classList.add("open")}if(svg.closest(".variant-c,.interactive-graph")){svg._graph.pinnedLayer=g.dataset.id;showLayerInspector(svg,g.dataset.id,g)}}
function setStage(svg,stage){svg._graph.stage=stage;svg.classList.remove("stage-0","stage-1","stage-2","stage-3","stage-4");svg.classList.add(`stage-${stage}`);svg.querySelectorAll(".layer").forEach(x=>x.classList.toggle("selected",stage>=1&&x.dataset.id==="build"));svg.querySelectorAll(".invoke-complete").forEach(x=>x.classList.toggle("hot",stage>=3));const p=svg._graph.pointer,label=p.querySelector("text");if(stage===4){const build=svg._graph.map.build,x=Math.min(1110,build.cx+150),responseY=Math.min(620,build.cy+265);p.setAttribute("transform",`translate(${x-115} ${responseY})`);label.textContent="CURRENT RESPONSE"}else{const [cx,cy]=svg._graph.config.center;p.setAttribute("transform",`translate(${cx} ${cy-112})`);label.textContent="CURRENT · I₀"}}
function runAction(svg,action){if(action==="expand"){svg.classList.add("dim-actions");setTimeout(()=>svg.classList.remove("dim-actions"),650)}else if(action==="invoke"){setStage(svg,2);setTimeout(()=>setStage(svg,3),550);setTimeout(()=>setStage(svg,4),1100)}else if(action==="complete"){setStage(svg,3)}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cancel prior invoke animation timers

Each Invoke click starts two new uncancelled timers. If the user clicks again before the first animation finishes, the callbacks interleave—for example, the first run can advance to stage 4 and the second run then regress it to stage 3—causing the response and current pointer to disappear and reappear out of sequence. Track and cancel the outstanding animation or disable the trigger until the run completes.

Useful? React with 👍 / 👎.

.variant-c .frame{background:#f7f5ef}.variant-c .grammar{left:14px;top:14px;flex-direction:column;align-items:flex-start}.variant-c .mock-question{position:absolute;z-index:4;left:50%;top:14px;width:min(430px,52%);transform:translateX(-50%);padding:9px 14px;border:1px solid #c9c0d5;border-radius:15px;background:rgba(255,253,248,.96);box-shadow:0 8px 26px rgba(34,49,63,.08);text-align:center}.variant-c .poster-title{position:absolute;z-index:3;left:16px;bottom:17px}.poster-title small{color:var(--orange);font-size:8px;font-weight:900;letter-spacing:.12em;text-transform:uppercase}.poster-title h2{margin:4px 0 0;font:500 22px/1.12 Georgia,serif}.variant-c .c-hint{left:auto;right:15px;bottom:14px}
.variant-d .frame{background:#faf9f5}.variant-d .mock-question{position:absolute;z-index:4;left:24px;top:20px;width:355px;padding:13px 16px;border-left:3px solid var(--purple);background:rgba(255,253,248,.94);box-shadow:0 9px 28px rgba(34,49,63,.08)}.variant-d .mock-question strong{text-align:left;font-size:15px}.variant-d .ports{display:flex;right:20px;top:20px}.variant-d .port{min-width:auto;border-radius:999px;padding:8px 12px}.variant-d .grammar{right:22px;bottom:17px}.variant-d .edition-label{position:absolute;z-index:3;left:25px;bottom:24px;max-width:250px;color:var(--muted);font-size:8px;font-weight:850;letter-spacing:.1em;text-transform:uppercase}.variant-d .layer .shell{fill:rgba(255,253,248,.88)}
.variant-e .frame{background:#f7f5ef}.variant-e .mock-question{position:absolute;z-index:4;left:22px;top:22px;width:300px;padding:13px 15px;border:1px solid #d5d0dd;border-radius:17px;background:rgba(255,253,248,.96);box-shadow:0 10px 28px rgba(34,49,63,.09)}.variant-e .mock-question strong{text-align:left}.variant-e .grammar{right:21px;bottom:20px;flex-direction:column;align-items:flex-start}.variant-e .ports{right:20px;top:20px}.variant-e .port.expand{display:none}.variant-e .frame:after{content:"SELECTED EXECUTION PATH";position:absolute;z-index:4;right:27px;top:111px;padding:5px 8px;border-radius:999px;background:rgba(255,253,248,.92);color:var(--purple);font-size:7px;font-weight:900;letter-spacing:.14em}.variant-e .layer:not(.layer-build) .shell{stroke-width:1.4}.variant-e .layer-build .shell{fill:#fbf8ff}
.variant-f .frame{display:grid;grid-template-columns:minmax(0,1fr) 292px;background:#f8f6f1}.variant-f .graph-pane{position:relative;min-width:0;overflow:hidden;border-right:1px solid #d2d6dc}.variant-f .mock-question{position:absolute;z-index:4;left:20px;top:18px;width:370px;padding:12px 15px;border:1px solid #d4cedd;border-radius:15px;background:rgba(255,253,248,.96);box-shadow:0 8px 24px rgba(34,49,63,.08)}.variant-f .mock-question strong{text-align:left}.variant-f .grammar{left:20px;bottom:18px}.detail-rail{position:relative;z-index:5;padding:24px 21px;background:rgba(255,253,248,.93)}.detail-rail small{color:var(--purple);font-size:8px;font-weight:900;letter-spacing:.13em;text-transform:uppercase}.detail-rail h2{margin:8px 0 5px;font:500 23px/1.08 Georgia,serif}.detail-rail>p{margin:0 0 18px;color:var(--muted);font-size:10px;line-height:1.5}.detail-grid{border-top:1px solid #dfe2e5}.detail-grid div{display:grid;grid-template-columns:54px 1fr;gap:9px;padding:10px 0;border-bottom:1px solid #e2e4e7;font-size:9px;line-height:1.4}.detail-grid b{color:var(--muted)}.detail-grid span{color:var(--ink)}.path-stack{display:grid;grid-template-columns:1fr;gap:7px;margin:20px 0}.path-step{position:relative;padding:10px 11px;border:1px solid #d5d9dd;border-radius:12px;background:#fff;font-size:9px;font-weight:850}.path-step.purple{border-color:#b9a7cf;color:var(--purple)}.path-step.orange{border-color:#d8a98f;color:var(--orange)}.path-step:not(:last-child):after{content:"↓";position:absolute;left:50%;bottom:-13px;z-index:2;color:var(--muted)}.rail-action{width:100%;border:0;border-radius:999px;background:var(--purple);color:#fff;padding:10px 12px;font-size:9px;font-weight:900;cursor:pointer}.rail-note{position:absolute;left:21px;right:21px;bottom:22px;color:var(--muted);font-size:8px;line-height:1.45}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stack the node-detail rail on narrow viewports

On a phone-sized viewport, variant F retains a fixed 292px detail rail beside the graph even though the media query reduces the page to roughly the same total width. At 375px wide the graph pane is left only about 59px before borders and padding, while its 370px question card is clipped by the frame, making the diagram unusable; switch this frame to a single-column layout at the existing narrow breakpoint.

Useful? React with 👍 / 👎.

function completeGlyph(x,y,angle=0,extra=""){const g=S("g",{class:`complete ${extra}`,transform:`translate(${x} ${y}) rotate(${angle})`});g.append(S("rect",{x:-24,y:-9,width:48,height:18,rx:9,fill:"#fbfaf6"}),S("path",{d:"M-22 0 H22",stroke:colors.orange,"stroke-width":2}),S("path",{d:"M-14 -6 L-8 0 L-14 6 L-20 0 Z",fill:"#f8e9de",stroke:colors.orange}),S("circle",{r:4.5,fill:colors.orange}),S("path",{d:"M14 -6 L20 0 L14 6 L8 0 Z",fill:"#f8e9de",stroke:colors.orange}));return g}
function buildGraph(svg,layoutName){const config=layouts[layoutName],map={};svg.append(defs(),S("rect",{x:18,y:18,width:1164,height:684,rx:30,fill:"#fbfaf6",stroke:"#c5cad1","stroke-width":2}));config.layers.forEach(([id,x,y])=>map[id]={id,x,y,cx:x+53,cy:y+32});const edgeGroup=S("g"),completeGroup=S("g"),layerGroup=S("g"),actionGroup=S("g"),childGroup=S("g",{class:"invoke-chain"}),pointer=S("g",{class:"pointer",transform:`translate(${config.center[0]} ${config.center[1]-112})`});
commonEdges.filter(([a,b])=>map[a]&&map[b]).forEach(([a,b],i)=>{const A=map[a],B=map[b],mx=(A.cx+B.cx)/2,my=(A.cy+B.cy)/2,qx=mx-10,qy=my+12,px=.25*A.cx+.5*qx+.25*B.cx,py=.25*A.cy+.5*qy+.25*B.cy,angle=Math.atan2(B.cy-A.cy,B.cx-A.cx)*180/Math.PI,edge=S("path",{class:`edge ${i%4===0?"soft":""}`,d:`M${A.cx} ${A.cy} Q${qx} ${qy} ${B.cx} ${B.cy}`,"data-edge-index":i});edgeGroup.append(edge);if(i%3===0){const glyph=completeGlyph(px,py,angle);glyph.setAttribute("data-edge-index",i);glyph.setAttribute("data-anchor",`${px},${py}`);completeGroup.append(glyph)}});
config.layers.forEach(([id,x,y])=>{const g=layerShape(id,x,y,id==="build");g.addEventListener("click",e=>selectNode(svg,g,e));if(svg.closest(".variant-c,.interactive-graph")){g.querySelectorAll(".node").forEach(node=>{node.addEventListener("pointerenter",()=>showNodeTooltip(svg,id,+node.dataset.nodeIndex,node));node.addEventListener("pointerleave",()=>hideNodeTooltip(svg))})}layerGroup.append(g)});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make graph-layer inspection keyboard accessible

All layer inspection is wired only through click and pointer-hover listeners on SVG <g> elements. Those groups have no tabindex, interactive role, or key handler, so keyboard-only users cannot open the lens or inspectors that expose the prototype's core node details; make each selectable layer focusable and support Enter/Space activation alongside pointer input.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant