@topolo/cli 0.13.17 → 0.13.21
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.
- package/README.md +18 -4
- package/agent-packs/README.md +469 -1
- package/agent-packs/coverage_changed.py +1 -1
- package/agent-packs/docs/selected-artifacts.md +47 -0
- package/agent-packs/documentation/cloud-task-state.md +56 -0
- package/agent-packs/domain-contexts/README.md +25 -0
- package/agent-packs/domain-contexts/cicd.json +8 -0
- package/agent-packs/gui/feed-adapter.js +4 -0
- package/agent-packs/gui/index.html +9 -0
- package/agent-packs/gui/studio.css +14 -0
- package/agent-packs/gui/studio.js +121 -0
- package/agent-packs/gui/studio_server.py +355 -0
- package/agent-packs/roles/cicd/CLAUDE.md +24 -0
- package/agent-packs/specializations/cicd--cicd.json +5 -0
- package/agent-packs/specializations/cicd--pipeline-engineering.json +5 -0
- package/agent-packs/specializations/cicd--promotion-deployment.json +5 -0
- package/agent-packs/specializations/cicd--queue-recovery.json +5 -0
- package/agent-packs/specializations/cicd--release-verification.json +5 -0
- package/agent-packs/specializations/cicd--runner-capacity.json +5 -0
- package/agent-packs/specializations/intakes--engineering-02.json +5 -0
- package/agent-packs/specializations/intakes--engineering-03.json +5 -0
- package/agent-packs/specializations/intakes--engineering.json +5 -0
- package/agent-packs/specializations/intakes--operator-01.json +5 -0
- package/agent-packs/specializations/intakes--operator-02.json +5 -0
- package/agent-packs/specializations/operator-skills--audio-and-narration-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--brand-and-copywriting-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--capability-research-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--data-analysis-and-visualization.json +5 -0
- package/agent-packs/specializations/operator-skills--financial-evidence-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--operating-template-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--operator-skill-library.json +5 -0
- package/agent-packs/specializations/operator-skills--operator-skills.json +5 -0
- package/agent-packs/specializations/operator-skills--operator-workflow-evaluation.json +5 -0
- package/agent-packs/specializations/operator-skills--privacy-and-agreement-review.json +5 -0
- package/agent-packs/specializations/operator-skills--storytelling-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--ux-and-accessibility-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--video-production-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--visual-design-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--workflow-integration-specialist.json +5 -0
- package/agent-packs/specializations/operators--access-and-privacy.json +5 -0
- package/agent-packs/specializations/operators--accounting-evidence-review.json +5 -0
- package/agent-packs/specializations/operators--agreement-operations.json +5 -0
- package/agent-packs/specializations/operators--books-operator.json +5 -0
- package/agent-packs/specializations/operators--brand-operator.json +5 -0
- package/agent-packs/specializations/operators--commerce-operations.json +5 -0
- package/agent-packs/specializations/operators--communications-operator.json +5 -0
- package/agent-packs/specializations/operators--content-and-assets-operator.json +5 -0
- package/agent-packs/specializations/operators--customer-operations.json +5 -0
- package/agent-packs/specializations/operators--design-operator.json +5 -0
- package/agent-packs/specializations/operators--design-production-02.json +5 -0
- package/agent-packs/specializations/operators--design-production-03.json +5 -0
- package/agent-packs/specializations/operators--design-production-04.json +5 -0
- package/agent-packs/specializations/operators--device-and-feed-operator.json +5 -0
- package/agent-packs/specializations/operators--director-operator.json +5 -0
- package/agent-packs/specializations/operators--experience-review.json +5 -0
- package/agent-packs/specializations/operators--growth-operations.json +5 -0
- package/agent-packs/specializations/operators--learn-operator.json +5 -0
- package/agent-packs/specializations/operators--localization-operator.json +5 -0
- package/agent-packs/specializations/operators--operators.json +5 -0
- package/agent-packs/specializations/operators--pay-operator.json +5 -0
- package/agent-packs/specializations/operators--research-and-intelligence.json +5 -0
- package/agent-packs/specializations/operators--roadmap-operator.json +5 -0
- package/agent-packs/specializations/operators--social-content-operator.json +5 -0
- package/agent-packs/specializations/operators--talent-operations.json +5 -0
- package/agent-packs/specializations/operators--web-operator.json +5 -0
- package/agent-packs/specializations/operators--workflow-operator.json +5 -0
- package/agent-packs/specializations/specialists--adversarial-qa.json +5 -0
- package/agent-packs/specializations/specialists--backlog-dispatch.json +5 -0
- package/agent-packs/specializations/specialists--build-01.json +5 -0
- package/agent-packs/specializations/specialists--build-02.json +5 -0
- package/agent-packs/specializations/specialists--build-03.json +5 -0
- package/agent-packs/specializations/specialists--developer-environment-steward.json +5 -0
- package/agent-packs/specializations/specialists--documentation-knowledge-steward.json +5 -0
- package/agent-packs/specializations/specialists--execution-readiness.json +5 -0
- package/agent-packs/specializations/specialists--incident-triage.json +5 -0
- package/agent-packs/specializations/specialists--platform-package-steward.json +5 -0
- package/agent-packs/specializations/specialists--product-direction-01.json +5 -0
- package/agent-packs/specializations/specialists--product-direction-02.json +5 -0
- package/agent-packs/specializations/specialists--release-recovery.json +5 -0
- package/agent-packs/specializations/specialists--release-watch.json +5 -0
- package/agent-packs/specializations/specialists--roadmap-planning-01.json +5 -0
- package/agent-packs/specializations/specialists--roadmap-planning-02.json +5 -0
- package/agent-packs/specializations/specialists--security-permissions-review.json +5 -0
- package/agent-packs/specializations/specialists--system-architecture-01.json +5 -0
- package/agent-packs/specializations/specialists--system-architecture-02.json +5 -0
- package/agent-packs/specializations/specialists--translation.json +5 -0
- package/agent-packs/specializations/specialists--ux-consistency-review.json +5 -0
- package/agent-packs/specializations/specialists--workflow-improvement.json +5 -0
- package/agent-packs/specializations/specialists--worktree-retirement.json +5 -0
- package/agent-packs/src/topolo_agent_pack/cicd.py +101 -0
- package/agent-packs/src/topolo_agent_pack/claude.py +22 -7
- package/agent-packs/src/topolo_agent_pack/claude_activation.py +157 -0
- package/agent-packs/src/topolo_agent_pack/claude_completion_recovery.py +216 -0
- package/agent-packs/src/topolo_agent_pack/claude_desktop.py +181 -0
- package/agent-packs/src/topolo_agent_pack/claude_dispatch.py +240 -0
- package/agent-packs/src/topolo_agent_pack/claude_prompt.py +74 -0
- package/agent-packs/src/topolo_agent_pack/claude_runner.py +207 -0
- package/agent-packs/src/topolo_agent_pack/claude_service.py +397 -0
- package/agent-packs/src/topolo_agent_pack/cli.py +188 -18
- package/agent-packs/src/topolo_agent_pack/closed_ledger_fence.py +118 -0
- package/agent-packs/src/topolo_agent_pack/cloud_attach.py +128 -0
- package/agent-packs/src/topolo_agent_pack/cloud_cutover.py +134 -0
- package/agent-packs/src/topolo_agent_pack/cloud_delta.py +162 -0
- package/agent-packs/src/topolo_agent_pack/cloud_migrate.py +142 -0
- package/agent-packs/src/topolo_agent_pack/cloud_recovery.py +42 -0
- package/agent-packs/src/topolo_agent_pack/cloud_work.py +126 -0
- package/agent-packs/src/topolo_agent_pack/coordinator_activity.py +27 -0
- package/agent-packs/src/topolo_agent_pack/coordinator_context.py +91 -0
- package/agent-packs/src/topolo_agent_pack/coordinator_refresh.py +116 -0
- package/agent-packs/src/topolo_agent_pack/desktop_acceptance.py +45 -0
- package/agent-packs/src/topolo_agent_pack/desktop_launcher.py +114 -0
- package/agent-packs/src/topolo_agent_pack/domain_context.py +38 -0
- package/agent-packs/src/topolo_agent_pack/execution.py +104 -7
- package/agent-packs/src/topolo_agent_pack/fresh.py +1 -1
- package/agent-packs/src/topolo_agent_pack/frontdoor.py +23 -0
- package/agent-packs/src/topolo_agent_pack/linked_task_context.py +80 -0
- package/agent-packs/src/topolo_agent_pack/live.py +285 -18
- package/agent-packs/src/topolo_agent_pack/native.py +160 -4
- package/agent-packs/src/topolo_agent_pack/policy.py +8 -0
- package/agent-packs/src/topolo_agent_pack/review_context.py +33 -0
- package/agent-packs/src/topolo_agent_pack/role_catalog.py +137 -0
- package/agent-packs/src/topolo_agent_pack/role_context.py +63 -0
- package/agent-packs/src/topolo_agent_pack/role_refresh.py +50 -0
- package/agent-packs/src/topolo_agent_pack/task_models.py +11 -7
- package/agent-packs/src/topolo_agent_pack/throughput.py +149 -0
- package/agent-packs/src/topolo_agent_pack/topolo.py +7 -1
- package/agent-packs/src/topolo_agent_pack/work.py +322 -0
- package/agent-packs/src/topolo_agent_pack/work_adoption.py +122 -0
- package/agent-packs/src/topolo_agent_pack/work_artifacts.py +84 -0
- package/agent-packs/src/topolo_agent_pack/work_context.py +77 -0
- package/agent-packs/src/topolo_agent_pack/work_gui.py +51 -0
- package/agent-packs/src/topolo_agent_pack/work_native.py +414 -0
- package/agent-packs/src/topolo_agent_pack/work_wait.py +151 -0
- package/agent-packs/templates/topolo-coordinators/AGENTS.md +7 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/cicd/AGENTS.md +6 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/cicd/config.json +4 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/engineering/AGENTS.md +3 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/engineering/config.json +4 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/operator-01/AGENTS.md +3 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/operator-01/config.json +4 -0
- package/agent-packs/templates/topolo-coordinators/pack.json +56 -0
- package/dist/chunk-NYDIAYVY.js +144 -0
- package/dist/{claude-pack-backend-YEVPAOID.js → chunk-UY5LEU5C.js} +1659 -21
- package/dist/claude-desktop-profile-JCQKSY3K.js +13 -0
- package/dist/claude-pack-activation-Q25MCEPU.js +506 -0
- package/dist/claude-pack-backend-HUTKGIDS.js +90 -0
- package/dist/index.js +408 -164
- package/package.json +8 -6
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"workspaceId": "ws_01kyn391hd2x4t9w5g7y70r9a0",
|
|
3
|
+
"documentId": "doc_d799a130-0e5d-40ae-9edd-74c94d8f5afa",
|
|
4
|
+
"versionId": "docver_7be8472d-c8f0-4677-820b-96246e8b52b4",
|
|
5
|
+
"bodySHA256": "8bbfb0c88603e7941310261bfb425d831b90b77319ceb920a2525412149adfe5",
|
|
6
|
+
"heading": "CI/CD operating bootstrap",
|
|
7
|
+
"sectionSHA256": "602dbe2618a5b851f40d269bc68632142ba543f2f223f95718fd21c92035fd26"
|
|
8
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
const appName=value=>typeof value==='string'?value.replace(/^Topolo (?=(Admin|Agent|Auth|Blog|CRM|Design|Docs|Engage|Nurture|Quro|Web)(?:$| | \/))/,''):value;
|
|
2
|
+
async function savedJSON(url){const controller=new AbortController();const endpoint=new URL(url,location.href);const cloud=endpoint.origin===location.origin&&['/updates.json','/api/work'].includes(endpoint.pathname);const timer=setTimeout(()=>controller.abort(),cloud?240000:5000);try{const response=await fetch(url,{cache:'no-store',signal:controller.signal});if(!response.ok)throw Error('Saved feed unavailable');return await response.json()}finally{clearTimeout(timer)}}
|
|
3
|
+
async function combineFeeds(base){let manifest;try{manifest=await savedJSON('feeds.json')}catch{base.sourceNotice='Lane manifest unavailable; showing the current engineering feed only.';return base}let notices=[];base.tasks=(base.tasks||[]).map(t=>({...t,application:appName(t.application),laneId:t.laneId||'engineering-shared',laneTitle:t.laneTitle||'Engineering · origin mapping pending'}));base.completed=(base.completed||[]).map(t=>({...t,application:appName(t.application),laneId:t.laneId||'engineering-shared',laneTitle:t.laneTitle||'Engineering · origin mapping pending'}));const feeds=manifest.feeds||[];const results=await Promise.allSettled(feeds.map(async feed=>{const u=new URL(feed.url,location.href);if(u.origin!==location.origin)throw Error('Feed must be local');return savedJSON(u)}));for(let index=0;index<feeds.length;index++){const feed=feeds[index];try{const result=results[index];if(result.status!=='fulfilled')throw Error('Lane feed unavailable');let d=result.value;if(d.schemaVersion!==1||d.writerLane!==feed.laneId||!Array.isArray(d.items))throw Error('Invalid lane feed');for(const item of d.items){let mappedImages=(item.imageFiles||[]).map(a=>{let pinned=manifest.imageAssets?.[a.sha256];if(pinned)return pinned;let dir=feed.imageDirectory,name=dir&&typeof a.path==='string'&&a.path.startsWith(dir.path)?a.path.slice(dir.path.length):'';return /^[A-Za-z0-9_-]+\.(png|jpg|jpeg|webp)$/i.test(name)?{url:dir.url+encodeURIComponent(name),label:name.replace(/\.[^.]+$/,'').replace(/-/g,' ')}:null}).filter(Boolean);if(mappedImages.length<(item.imageFiles||[]).length)notices.push(item.title+' has unmapped review images');let row={...item,application:appName(item.application),reviewImages:mappedImages,links:[...(item.links||[])],id:feed.laneId+':'+item.id,laneId:feed.laneId,laneTitle:feed.laneTitle,status:item.statusLabel,kind:/held|blocked|stop/.test(item.state)?'held':item.kind,verifiedAt:item.evidenceAtUTC||item.verifiedAtUTC||null,laneUpdatedAt:d.updatedAtUTC,evidence:Array.isArray(item.evidence)?item.evidence:[item.evidence].filter(Boolean),contributors:item.contributors||(item.attribution||[]).map(name=>({role:name,contribution:'Listed by the originating lane.',proof:'See lane evidence for contribution scope'})),needsHuman:item.needsHuman===true||(item.needsHuman!==false&&(/manual_customer_delivery|decision|for_review|needs_user/.test(item.state)||(/\bAsh reviews\b/.test(item.next||'')&&Boolean(item.decisionId)))),deliveryBoundary:item.closed===true?'Originating lane reports the scoped work closed.':item.kind==='engineering-handoff'?'Verified engineering delivery; accepted handoff alone does not close the issue.':item.customerDelivered?'Customer delivery reported by originating lane.':'Production artifact and customer delivery are separate; this request remains open.',roadmap:item.roadmap?{...item.roadmap,itemId:item.roadmap.itemId}:null};(item.closed===true?base.completed:base.tasks).push(row)}if(d.updatedAtUTC>base.updatedAt)base.updatedAt=d.updatedAtUTC}catch{notices.push(feed.laneTitle+' feed unavailable')}}base.sourceNotice=manifest.notice+(notices.length?' · '+notices.join('; '):'');return base}
|
|
4
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Topolo · Decision Studio</title><link rel="stylesheet" href="studio.css"></head><body>
|
|
3
|
+
<header><div class="wordmark">topolo<span>.ai</span><span class="product">Decision studio</span></div><button id="queue-toggle" class="work-finder" aria-haspopup="dialog" aria-expanded="false" aria-controls="queue">Find work</button><div class="head-actions"><button id="request-work" class="primary">Request work</button><span class="fresh" id="updated">Loading saved feed…</span><button id="refresh" class="icon-button" aria-label="Refresh saved feeds">↻</button></div></header>
|
|
4
|
+
<nav class="navigation" role="tablist" aria-label="Workspace views"><button data-view="review" role="tab" aria-selected="true">Decisions <span id="review-count" class="count">0</span></button><button data-view="work" role="tab" aria-selected="false">Work <span id="work-count" class="count">0</span></button><button data-view="recorded" role="tab" aria-selected="false">Your responses <span id="recorded-count" class="count">0</span></button><button data-view="releases" role="tab" aria-selected="false">Releases</button><button data-view="activity" role="tab" aria-selected="false">Activity</button></nav>
|
|
5
|
+
<div id="notice" class="notice" hidden role="status"></div><section class="view-guide" aria-label="About this view"><p id="view-description"></p><p id="decision-coverage" class="coverage-note" hidden></p><div id="review-backlog" hidden></div></section><dialog class="work-picker" id="queue" aria-labelledby="queue-title"><div class="picker-body"><div class="queue-heading"><h2 id="queue-title">Find work</h2><button id="close-queue" class="icon-button" aria-label="Close work finder">×</button></div><input class="search" id="search" type="search" placeholder="Find a decision…" aria-label="Search work"><div class="filters"><select id="lane" aria-label="Intake lane"><option value="">All lanes</option></select><select id="state" aria-label="Work filter"><option value="">All items</option><option value="blocked">Blocked</option><option value="media">With previews</option><option value="completed">Completed work</option></select></div><p id="picker-summary" class="small" role="status"></p><div id="queue-list"></div></div></dialog><div class="workspace"><main class="content" id="focus" aria-live="polite"><p class="empty">Loading your shared feed…</p></main></div>
|
|
6
|
+
<footer class="response-bar" id="response-bar" hidden><div class="response-inner"><div class="response-text" id="response-context"></div><div class="response-actions"><button id="change-action">Request changes</button><button id="primary-action" class="primary">Send feedback</button><button id="hold-action" class="quiet">Ask to hold</button></div></div></footer>
|
|
7
|
+
<dialog id="response-dialog" aria-labelledby="response-title"><form id="response-form"><h2 id="response-title">Send feedback</h2><p id="response-scope"></p><input type="hidden" id="intent"><label for="response-note" class="small">Your response</label><textarea id="response-note" required maxlength="8000" placeholder="What would you like the owner to know?"></textarea><p>Saved to the shared local decision inbox. The owner must accept and act on it; this does not run a release, grant access or publish content.</p><p id="response-error" role="alert"></p><div class="actions"><button type="button" data-close="response-dialog">Cancel</button><button id="submit-response" class="primary" type="submit">Save response</button></div></form></dialog>
|
|
8
|
+
<dialog class="image-dialog" id="image-dialog" aria-label="Expanded preview"><div class="actions"><button data-close="image-dialog">Close preview ×</button></div><img id="full-image" alt=""></dialog><dialog id="copy-dialog" aria-label="Copy work reference"><h2>Work reference</h2><textarea id="copy-text" readonly></textarea><div class="actions"><button data-close="copy-dialog">Done</button></div></dialog><div class="toast" id="toast" role="status" hidden></div>
|
|
9
|
+
<dialog id="work-dialog" aria-labelledby="work-request-title"><form id="work-form"><h2 id="work-request-title">What would you like done?</h2><label for="work-objective">Objective and acceptance</label><textarea id="work-objective" required maxlength="16000" placeholder="Describe the outcome you want and what would demonstrate it is complete."></textarea><label for="work-channel">Work area</label><select id="work-channel"><option value="engineering">Engineering</option><option value="operations">Operations</option><option value="cicd">CI/CD</option></select><p>Your coordinator routes this request. Progress and evidence stay attached to one task; specialists are created only while needed.</p><p id="work-error" role="alert"></p><div class="actions"><button type="button" data-close="work-dialog">Cancel</button><button id="work-submit" class="primary" type="submit">Send to coordinator</button></div></form></dialog><script src="feed-adapter.js"></script><script src="studio.js"></script></body></html>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
:root{font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#17212e;background:#f6f8fa;font-synthesis:none;--line:#e4e9ee;--muted:#687789;--blue:#287b9d;--pale:#eaf4f8}*{box-sizing:border-box}body{margin:0}button,input,textarea,select{font:inherit}button,a{touch-action:manipulation}button{cursor:pointer}button:disabled{cursor:default;opacity:.45}button,a,input,textarea,select{outline-offset:4px}button:focus-visible,a:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible{outline:2px solid var(--blue)}button{border:1px solid var(--line);background:#fff;color:inherit;border-radius:9px;padding:10px 13px;transition:background .15s,transform .15s}button:hover:not(:disabled){background:#edf3f6}button:active:not(:disabled){transform:translateY(1px)}a{color:#246c8a;text-decoration:none}a:hover{text-decoration:underline}header{height:66px;background:#fff;display:flex;align-items:center;justify-content:space-between;padding:0 25px;border-bottom:1px solid var(--line);gap:12px;position:sticky;top:0;z-index:5}.wordmark{font-size:23px;font-weight:800;letter-spacing:-1.2px}.wordmark span{color:var(--blue)}.product{font-size:12px;color:var(--muted);letter-spacing:0;font-weight:500;border-left:1px solid var(--line);padding-left:13px;margin-left:13px}.head-actions{display:flex;align-items:center;gap:10px}.fresh{font-size:11px;color:var(--muted)}.icon-button{font-size:19px;padding:5px 10px}.navigation{background:#fff;display:flex;align-items:center;gap:4px;padding:0 20px;border-bottom:1px solid var(--line);overflow:auto}.navigation button{border:0;background:none;border-radius:0;white-space:nowrap;padding:16px 10px;font-size:12px;border-bottom:2px solid transparent;color:var(--muted)}.navigation button[aria-selected=true]{border-bottom-color:var(--blue);color:#1b627d;font-weight:650}.count{font-size:10px;display:inline-flex;align-items:center;justify-content:center;min-width:18px;margin-left:5px;padding:2px 5px;border-radius:5px;background:#f0f3f6;color:#657486}.navigation button[aria-selected=true] .count{background:var(--pale);color:#23667f}.notice{margin:12px 24px;background:#fff2df;border:1px solid #f0d5a8;border-radius:8px;padding:12px;font-size:12px}.workspace{display:grid;grid-template-columns:270px minmax(0,1fr);max-width:1600px;margin:auto;min-height:calc(100svh - 114px)}.queue{border-right:1px solid var(--line);padding:22px 15px;background:#f9fafb}.queue-heading{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.eyebrow{font-size:10px;font-weight:650;text-transform:uppercase;letter-spacing:1.4px;color:var(--blue)}.queue-heading .eyebrow{color:var(--muted)}.search{width:100%;padding:10px 12px;border:1px solid var(--line);border-radius:8px;font-size:12px;background:#fff}.filters{display:flex;gap:7px;margin:9px 0 16px}.filters select{width:50%;min-width:0;border:1px solid var(--line);border-radius:6px;background:#fff;color:var(--muted);padding:7px;font-size:11px}.queue-item{display:block;width:100%;text-align:left;border:1px solid transparent;background:transparent;padding:13px 12px;margin:3px 0}.queue-item.selected{background:#fff;border-color:#d4e3eb;box-shadow:0 2px 5px #16233305}.queue-item .name{display:block;font-size:13px;line-height:1.45;font-weight:620;margin:5px 0}.queue-item .small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.small{font-size:11px;color:var(--muted);line-height:1.5}.queue-item .state{font-size:10px;color:#526d7a}.dot{display:inline-block;width:5px;height:5px;border-radius:50%;background:#cc9348;margin-right:6px}.dot.done{background:#49967c}.content{min-width:0;padding:30px 32px 150px;background:#fff}.context-line{display:flex;justify-content:space-between;gap:10px;align-items:center}.pager{display:flex;gap:6px;align-items:center}.pager span{font-size:11px;color:var(--muted);margin-right:5px}.pager button{padding:4px 10px;font-size:20px}h1{font-size:clamp(24px,3vw,35px);line-height:1.16;letter-spacing:-1px;font-weight:660;margin:14px 0 10px;max-width:850px}.subtitle{font-size:13px;line-height:1.6;color:var(--muted);margin:0 0 23px;max-width:800px}.detail-layout{display:grid;grid-template-columns:minmax(0,1fr) 240px;gap:25px}.preview-area{min-width:0}.preview{height:min(52svh,530px);min-height:240px;background:#f3f5f7;border-radius:12px;border:1px solid var(--line);display:flex;align-items:center;justify-content:center;overflow:hidden;position:relative}.preview img{height:100%;width:100%;object-fit:contain}.preview button{position:absolute;right:12px;bottom:12px;font-size:11px;box-shadow:0 2px 8px #17212e15}.preview-caption{display:flex;justify-content:space-between;gap:12px;font-size:11px;color:var(--muted);margin:12px 0}.thumbnails{display:flex;gap:9px;overflow:auto;padding:2px 2px 9px}.thumb{padding:3px;flex:0 0 95px;border-radius:7px;background:#f7f9fa}.thumb img{width:100%;height:68px;object-fit:cover;object-position:top;border-radius:4px}.thumb span{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:10px;padding:5px 3px}.thumb[aria-pressed=true]{border:2px solid var(--blue);padding:2px}.inspector{border-left:1px solid var(--line);padding-left:23px}.inspector section{padding:0 0 18px;margin:0 0 19px;border-bottom:1px solid var(--line)}h2{font-size:12px;margin:0 0 10px;font-weight:650}.inspector p,.detail-copy{font-size:12px;line-height:1.7;color:var(--muted);margin:0}.owner{display:flex;align-items:center;gap:9px;margin-bottom:10px}.avatar{background:var(--pale);color:var(--blue);border-radius:50%;height:30px;width:30px;display:grid;place-items:center;font-size:10px;font-weight:600}.owner-name{font-size:12px;font-weight:600}.support-link{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:1px solid #eff2f5;font-size:12px}.status-panel{padding:22px;border:1px solid var(--line);border-radius:10px;background:#fafcfd;min-height:250px}.status-panel h2{font-size:18px;line-height:1.5;letter-spacing:-.3px}.status-pill{font-size:10px;display:inline-block;padding:5px 8px;border-radius:5px;background:var(--pale);color:#23677f;margin-bottom:15px}.stage-track{display:flex;gap:6px;margin:24px 0;flex-wrap:wrap}.stage{font-size:10px;padding:7px 10px;background:#f0f3f6;color:var(--muted);border-radius:5px}.stage.pass{background:#eaf5ef;color:#36775b}.details{margin-top:20px;border-top:1px solid var(--line);padding-top:15px}.details summary{font-size:12px;color:#496575;cursor:pointer}.details p{font-size:12px;line-height:1.75;color:var(--muted)}pre{font:11px/1.6 ui-monospace,monospace;white-space:pre-wrap;word-break:break-word;background:#f6f8fa;padding:12px;border-radius:6px}.response-bar{position:fixed;bottom:0;right:0;left:270px;background:#ffffffef;backdrop-filter:blur(15px);border-top:1px solid var(--line);padding:14px 32px;z-index:4}.response-inner{max-width:1250px;margin:auto;display:flex;align-items:center;justify-content:space-between;gap:18px}.response-text{font-size:11px;color:var(--muted);max-width:400px;line-height:1.5}.response-actions{display:flex;gap:7px;flex-wrap:wrap}.primary{background:var(--blue);border-color:var(--blue);color:#fff}.primary:hover:not(:disabled){background:#226e8d}.quiet{background:transparent}.response-actions button{font-size:12px}.mobile-queue{display:none}.empty{padding:50px 20px;text-align:center;color:var(--muted);font-size:13px}.activity-row{border-bottom:1px solid var(--line);padding:16px 0}.activity-row strong{font-size:13px}.activity-row p{font-size:12px;color:var(--muted);line-height:1.6}.activity-row time{font-size:10px;color:var(--muted)}.table-wrap{overflow:auto}table{border-collapse:collapse;width:100%;font-size:12px}th,td{text-align:left;vertical-align:top;padding:14px 10px;border-bottom:1px solid var(--line);min-width:120px;line-height:1.6}th{font-size:10px;text-transform:uppercase;letter-spacing:1px;color:var(--muted)}dialog{width:min(520px,calc(100% - 30px));border:1px solid var(--line);border-radius:14px;padding:25px;box-shadow:0 25px 100px #15233330;color:inherit}dialog::backdrop{background:#17212e60;backdrop-filter:blur(3px)}dialog h2{font-size:21px;letter-spacing:-.5px}dialog p{font-size:12px;line-height:1.6;color:var(--muted)}dialog select,textarea{display:block;width:100%;padding:10px;border:1px solid #d8e1e8;border-radius:8px;margin:12px 0;font-size:13px}textarea{min-height:120px;resize:vertical}dialog .actions{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.toast{position:fixed;bottom:105px;left:50%;transform:translateX(-50%);background:#173e50;color:white;border-radius:8px;padding:12px 18px;font-size:12px;z-index:20;max-width:90%}.image-dialog{width:min(1000px,95vw);max-height:95svh;padding:15px}.image-dialog img{width:100%;height:78svh;object-fit:contain}.image-dialog .actions{margin:0 0 10px}.record{background:#eef6f2;padding:12px 15px;border-radius:8px;font-size:12px;line-height:1.65;margin-top:15px;color:#356b55}.intro{font-size:12px;color:var(--muted);line-height:1.6;margin:0 0 25px}.queue-item{animation:appear .15s ease-out}.focus-content{animation:appear .2s ease-out}@keyframes appear{from{opacity:.5;transform:translateY(3px)}to{opacity:1;transform:translateY(0)}}[hidden]{display:none!important}@media(min-width:1600px){.response-bar{left:max(270px,calc((100vw - 1600px)/2 + 270px));right:max(0px,calc((100vw - 1600px)/2))}}@media(max-width:1150px){.detail-layout{grid-template-columns:1fr}.inspector{border-left:0;padding-left:0;display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:25px}.content{padding:24px 25px 155px}.response-bar{padding:12px 25px}.workspace{grid-template-columns:235px minmax(0,1fr)}.response-bar{left:235px}}@media(max-width:850px){header{padding:0 20px;height:58px}.product{display:none}.workspace{display:block;min-height:calc(100svh - 108px)}.queue{display:none;position:fixed;inset:108px 0 0;z-index:6;overflow:auto;padding:18px 20px;border-right:0}.queue.open{display:block}.mobile-queue{display:inline-flex;gap:8px;align-items:center;font-size:11px;border-radius:20px;background:#f6f8fb}.content{padding:23px 22px 150px}.response-bar{left:0;padding:13px 22px}.response-inner{gap:10px}.response-text{max-width:250px}.preview{height:min(47svh,450px)}.fresh{display:none}.navigation{padding:0 12px}.navigation button{padding:15px 9px}.inspector{grid-template-columns:1fr 1fr}.queue-item .small{white-space:normal}.queue-item{padding:14px}.head-actions{gap:7px}}@media(max-width:480px){header{padding:0 15px}.wordmark{font-size:22px}.mobile-queue{max-width:170px;overflow:hidden;white-space:nowrap}.content{padding:22px 17px 170px}h1{font-size:27px;letter-spacing:-.7px}.subtitle{font-size:12px;margin-bottom:18px}.context-line{gap:6px}.eyebrow{font-size:9px;letter-spacing:1px}.inspector{display:block}.response-bar{padding:12px 17px}.response-inner{display:block}.response-text{max-width:none;margin-bottom:10px;font-size:10px}.response-actions{display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px}.response-actions button{padding:10px 8px;font-size:11px}.navigation button{font-size:11px;padding:14px 7px}.count{margin-left:3px}.preview{height:40svh;min-height:220px}.preview-caption{font-size:10px}.status-panel{padding:19px}.toast{bottom:140px}.pager span{display:none}}@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
|
2
|
+
|
|
3
|
+
.workspace.wide{display:block}.workspace.wide .content{min-height:calc(100svh - 114px)}
|
|
4
|
+
|
|
5
|
+
.status-panel .detail-copy,.inspector section>p{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:4;overflow:hidden}.inspector .small{-webkit-line-clamp:2}
|
|
6
|
+
|
|
7
|
+
.workflow-progress{margin:18px 0;padding:18px;border:1px solid var(--line);border-radius:9px;font-size:12px;line-height:1.55}.workflow-progress h2{margin:0 0 12px;font-size:15px}.workflow-steps{list-style:none;margin:18px 0 0;padding:0}.workflow-steps li{border-left:2px solid #dce4e9;padding:0 0 18px 16px;position:relative}.workflow-steps li:before{content:"";position:absolute;left:-5px;top:5px;width:8px;height:8px;border-radius:50%;background:#cdd7de}.workflow-steps li.verified{border-color:#459078}.workflow-steps li.verified:before{background:#459078}.workflow-steps strong{display:inline-block;margin-right:12px}.workflow-steps p{margin:5px 0 0}.workflow-steps li:last-child{padding-bottom:0}
|
|
8
|
+
|
|
9
|
+
.view-guide{padding:14px 24px;border-bottom:1px solid var(--line);background:#fff}.view-guide p{margin:0;font-size:12px;line-height:1.65;color:#496575}.view-guide .coverage-note{margin-top:6px;color:#735a30}@media(max-width:480px){.view-guide{padding:12px 17px}.view-guide p{font-size:11px}}
|
|
10
|
+
|
|
11
|
+
/* Single-item workspace; the work finder is a dialog, never a sidebar. */
|
|
12
|
+
.workspace{display:block;max-width:1240px}.response-bar{left:0;right:0}.response-inner{max-width:1176px}.work-finder{margin-left:auto;white-space:nowrap;font-size:12px}.work-picker{width:min(600px,calc(100vw - 32px));max-height:82svh;padding:0;background:#fff;overflow:auto}.work-picker .picker-body{padding:22px}.work-picker .queue-heading{margin-bottom:18px}.work-picker h2{font-size:19px;margin:0}.work-picker .filters{margin-bottom:12px}.work-picker .filters select{margin:0;width:50%}.work-picker #picker-summary{margin:0 0 12px}.work-picker .queue-item{padding:12px 9px;margin:0;border-radius:0;border-bottom:1px solid var(--line);animation:none}.work-picker .queue-item.selected{box-shadow:none;border-color:var(--line);background:var(--pale)}.work-picker .queue-item .name{font-size:13px;margin:4px 0}.work-picker .queue-item .eyebrow{font-size:9px}.work-picker[open]{display:block}.head-actions{flex-shrink:0}
|
|
13
|
+
@media(max-width:850px){.workspace{display:block}.work-finder{font-size:11px}.response-bar{left:0;right:0}}
|
|
14
|
+
@media(max-width:480px){.work-picker .picker-body{padding:17px}.work-picker{max-height:85svh}.work-finder{padding:8px 10px}header{gap:8px}.work-picker .filters select{font-size:12px}}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const $ = id => document.getElementById(id);
|
|
3
|
+
const node = (tag, value = '', cls = '') => { const n = document.createElement(tag); n.textContent = value; if (cls) n.className = cls; return n; };
|
|
4
|
+
const fmt = t => t && !Number.isNaN(Date.parse(t)) ? new Intl.DateTimeFormat('en-GB', {timeZone:'Asia/Dubai', day:'2-digit', month:'short', hour:'2-digit', minute:'2-digit'}).format(new Date(t)) : 'Not recorded';
|
|
5
|
+
let data, inbox = {responses:[], revisions:{}}, selected = localStorage.getItem('topolo-studio-selection') || '', view = 'review', chosen = -1, refreshing = false, refreshPending = false, lastFingerprint = '', submissionId = null, responseItem = null;
|
|
6
|
+
let lastSeen = localStorage.getItem('topolo-catch-up-last-read') || '';
|
|
7
|
+
const viewState = new Map();
|
|
8
|
+
let initialHash = ''; try { initialHash = decodeURIComponent(location.hash.slice(1)); } catch {}
|
|
9
|
+
function toast(value) { $('toast').textContent = value; $('toast').hidden = false; clearTimeout(toast.timer); toast.timer = setTimeout(() => $('toast').hidden = true, 4500); }
|
|
10
|
+
function safeURL(value) { try { const u = new URL(value, location.href); return ['http:','https:'].includes(u.protocol) || (u.protocol === 'codex:' && u.hostname === 'threads') ? u.href : null; } catch { return null; } }
|
|
11
|
+
function needsReview(t) { return t.needsHuman === true; }
|
|
12
|
+
function isBlocked(t) { return t.blocked || t.kind === 'held' || /blocked|stopped|held|hold|gap|refus/i.test(t.status || ''); }
|
|
13
|
+
function latestResponse(t) { return inbox.responses.filter(r => r.itemId === t.id).sort((a,b) => b.recordedAtUTC.localeCompare(a.recordedAtUTC))[0]; }
|
|
14
|
+
const workflowLabel = r => ({pending_delivery:'Waiting for delivery',route_unresolved:'Owner route needs verification',delivered:'Delivered · awaiting acceptance',accepted:'Accepted · in progress',completed:'Completed',blocked:'Blocked · owner action',rejected:'Owner declined',delivery_unknown:'Delivery needs reconciliation',verification_needed:'Evidence needs verification'}[r?.workflow?.state] || 'Saved · awaiting delivery');
|
|
15
|
+
function responseProgress(record) {
|
|
16
|
+
const section = node('section','','workflow-progress'), w = record.workflow;
|
|
17
|
+
section.append(node('h2','Response progress'),node('strong',workflowLabel(record)));
|
|
18
|
+
if(w?.owner) section.append(node('p','Responsible owner: ' + w.owner.title,'small'));
|
|
19
|
+
section.append(node('p',w?.summary || 'Saved locally. Owner acceptance has not been verified.'));
|
|
20
|
+
const steps = node('ol','','workflow-steps');
|
|
21
|
+
for(const [state,label] of [['saved','Saved'],['delivered','Delivered'],['accepted','Accepted'],['completed','Completed']]) {
|
|
22
|
+
const e = state === 'saved' ? {atUTC:record.recordedAtUTC,summary:'Your exact response was saved.'} : w?.events?.slice().reverse().find(e=>e.state===state);
|
|
23
|
+
const li = node('li','','' + (e ? 'verified':'pending'));
|
|
24
|
+
li.append(node('strong',label),node('span',e ? fmt(e.atUTC) : 'Not yet verified','small'));
|
|
25
|
+
if(e) li.append(node('p',e.summary)); steps.append(li);
|
|
26
|
+
}
|
|
27
|
+
section.append(steps); return section;
|
|
28
|
+
}
|
|
29
|
+
function currentResponse(t) { const r = latestResponse(t); return r && r.sourceRevision === inbox.revisions[t.id] ? r : null; }
|
|
30
|
+
function matches(t) { const q = $('search').value.toLowerCase(); return (!q || [t.title,t.summary,t.application,t.owner,t.laneTitle].join(' ').toLowerCase().includes(q)) && (!$('lane').value || t.laneId === $('lane').value) && (!$('state').value || ($('state').value === 'blocked' ? isBlocked(t) : $('state').value === 'media' ? t.reviewImages?.length : $('state').value === 'completed' ? true : needsReview(t))); }
|
|
31
|
+
function allItems() { return [...(data?.tasks || []), ...(data?.completed || [])]; }
|
|
32
|
+
function pendingDecisions() { return data.tasks.filter(t => needsReview(t) && !currentResponse(t)); }
|
|
33
|
+
function workItems(completed = false) { return (completed ? data.completed || [] : data.tasks).filter(t => !needsReview(t)); }
|
|
34
|
+
function viewRows() { return view === 'recorded' ? allItems().filter(t => latestResponse(t)) : view === 'review' ? pendingDecisions() : workItems(data.completed?.some(t => t.id === selected)); }
|
|
35
|
+
function rows() { return (view === 'work' ? workItems($('state').value === 'completed') : viewRows()).filter(matches); }
|
|
36
|
+
function closeFinder() { if ($('queue').open) $('queue').close(); $('queue-toggle').setAttribute('aria-expanded','false'); }
|
|
37
|
+
function renderPicker() {
|
|
38
|
+
const list = rows(), shown = list.slice(0,8);
|
|
39
|
+
$('picker-summary').textContent = list.length > 8 ? 'Showing 8 of ' + list.length + ' matches. Refine your search to find another item.' : list.length + (list.length === 1 ? ' match' : ' matches');
|
|
40
|
+
$('queue-list').replaceChildren(...shown.map(queueRow));
|
|
41
|
+
if (!shown.length) $('queue-list').append(node('p','No matches. Try another search or filter.','empty'));
|
|
42
|
+
}
|
|
43
|
+
function openFinder() { renderPicker(); $('queue').showModal(); $('queue-toggle').setAttribute('aria-expanded','true'); $('search').focus(); }
|
|
44
|
+
function reference(t) { return ['Topolo decision reference: ' + t.id, 'Work: ' + t.title, 'Lane: ' + (t.laneTitle || 'Engineering'), t.decisionId ? 'Decision: ' + t.decisionId : '', 'Status: ' + t.status, t.roadmap?.itemId ? 'Roadmap: ' + t.roadmap.itemId : '', 'View: ' + location.origin + '/#' + encodeURIComponent(t.id)].filter(Boolean).join('\n'); }
|
|
45
|
+
async function copyReference(t) { try { await navigator.clipboard.writeText(reference(t)); toast('Reference copied — paste it into any chat.'); } catch { $('copy-text').value = reference(t); $('copy-dialog').showModal(); $('copy-text').select(); } }
|
|
46
|
+
function selectItem(t) { if (!t || typeof t.id !== 'string' || !t.id) return false; selected = t.id; chosen = -1; localStorage.setItem('topolo-studio-selection', selected); history.replaceState(null, '', '#' + encodeURIComponent(selected)); closeFinder(); render(); return true; }
|
|
47
|
+
function openTask(taskId) {
|
|
48
|
+
const item = typeof taskId === 'string' && allItems().find(t => t.id === 'task:' + taskId && t.nativeTask?.id === taskId);
|
|
49
|
+
if (!item) { toast('This task is not in the current task feed. Refresh to load its recorded status.'); return false; }
|
|
50
|
+
view = latestResponse(item) ? 'recorded' : needsReview(item) ? 'review' : 'work';
|
|
51
|
+
for (const id of ['search','lane','state']) $(id).value = '';
|
|
52
|
+
if (view === 'work' && data.completed?.some(t => t.id === item.id)) $('state').value = 'completed';
|
|
53
|
+
return selectItem(item);
|
|
54
|
+
}
|
|
55
|
+
function setView(value) {
|
|
56
|
+
viewState.set(view, {selected, search:$('search').value, lane:$('lane').value, state:$('state').value});
|
|
57
|
+
view = value; const saved = viewState.get(view); selected = saved?.selected || ''; chosen = -1;
|
|
58
|
+
for (const id of ['search','lane','state']) $(id).value = saved?.[id] || '';
|
|
59
|
+
closeFinder(); render();
|
|
60
|
+
}
|
|
61
|
+
function anchor(label, url) { const safe = safeURL(url); if (!safe) return null; const a = node('a', label, 'support-link'); a.href = safe; a.target = '_blank'; a.rel = 'noopener noreferrer'; a.append(node('span','↗')); return a; }
|
|
62
|
+
function details(title, content) { const d = node('details','','details'); d.append(node('summary',title)); if (typeof content === 'string') d.append(node('p',content)); else d.append(content); return d; }
|
|
63
|
+
function renderReviewBacklog() {
|
|
64
|
+
const area = $('review-backlog'), records = data.decisionCoverage?.records || [], wasOpen = area.querySelector('details')?.open;
|
|
65
|
+
area.hidden = view !== 'review' || !records.length; area.replaceChildren(); if(area.hidden) return;
|
|
66
|
+
const list = node('ul','','review-backlog-list');
|
|
67
|
+
for(const record of records) { const row = node('li'); row.append(node('strong',record.title),node('p',record.reason)); list.append(row); }
|
|
68
|
+
const disclosure = details('Older review backlog · ' + records.length + ' records being reconciled',list); disclosure.open = Boolean(wasOpen); area.append(disclosure);
|
|
69
|
+
}
|
|
70
|
+
function queueRow(t) { const b = node('button','','queue-item' + (selected === t.id ? ' selected' : '')); b.type = 'button'; b.setAttribute('aria-current', selected === t.id ? 'true':'false'); b.append(node('span',t.application || t.laneTitle || 'Shared work','eyebrow'), node('span',t.title,'name')); const s = node('span','','state'); s.append(node('i','','dot' + (latestResponse(t) ? ' done':'')), document.createTextNode(latestResponse(t) ? workflowLabel(latestResponse(t)) : data.completed?.some(x => x.id === t.id) ? 'Completed' : needsReview(t) ? 'Awaiting your review' : isBlocked(t) ? 'Blocked · owner action' : 'In progress')); b.append(s); b.onclick = () => selectItem(t); return b; }
|
|
71
|
+
function showMedia(t, area) { const images = t.reviewImages || []; chosen = Math.min(chosen,images.length-1); const image = images[Math.min(chosen, images.length - 1)]; const box = node('div','','preview'); const img = node('img'); img.src = safeURL(image.url); img.alt = image.label || t.title; img.loading = 'eager'; img.onerror = () => { img.hidden = true; box.append(node('p','Preview unavailable. Open the source or send feedback.','small')); }; box.append(img); const expand = node('button','Expand preview ↗'); expand.onclick = () => { $('full-image').src = img.src; $('full-image').alt = img.alt; $('image-dialog').showModal(); }; box.append(expand); area.append(box); const caption = node('div','','preview-caption'); caption.append(node('span',image.label || 'Review preview'), node('span',String(chosen + 1) + ' / ' + images.length)); area.append(caption); const thumbs = node('div','','thumbnails'); images.slice(0,3).forEach((image,index) => { const b = node('button','','thumb'); b.setAttribute('aria-label','Preview ' + (index + 1) + ': ' + (image.label || 'image')); b.setAttribute('aria-pressed',String(chosen === index)); const i = node('img'); i.src = safeURL(image.url); i.alt = ''; i.loading = 'lazy'; b.append(i,node('span',image.label || 'Preview ' + (index + 1))); b.onclick = () => { chosen = index; renderFocus(t); }; thumbs.append(b); }); if (images.length > 3) { const more = node('button','+' + (images.length - 3) + ' more','thumb'); more.onclick = () => { chosen = (chosen + 1) % images.length; renderFocus(t); }; thumbs.append(more); } area.append(thumbs); }
|
|
72
|
+
function renderFocus(t) {
|
|
73
|
+
const content = $('focus'); content.replaceChildren(); content.className = 'content focus-content'; const context = node('div','','context-line'); context.append(node('span',t.application || (needsReview(t) ? 'Your review' : 'Shared work'),'eyebrow')); const list = viewRows(), index = list.findIndex(x => x.id === t.id); const pager = node('div','','pager'); pager.append(node('span',(index + 1) + ' of ' + list.length)); const prev = node('button','‹'), next = node('button','›'); prev.setAttribute('aria-label','Previous item'); next.setAttribute('aria-label','Next item'); prev.disabled = index <= 0; next.disabled = index >= list.length - 1; prev.onclick = () => selectItem(list[index - 1]); next.onclick = () => selectItem(list[index + 1]); pager.append(prev,next); context.append(pager); content.append(context,node('h1',t.title),node('p',needsReview(t) ? (t.reviewImages?.length ? t.reviewImages.length + ' directions ready. Select a preview and share your preference.' : t.status || 'Ready for your response') : t.status || 'Current work','subtitle'));
|
|
74
|
+
const layout = node('div','','detail-layout'), area = node('div','','preview-area');
|
|
75
|
+
if (t.reviewImages?.length) { if(chosen < 0) { const selectedAsset = latestResponse(t)?.response.asset; chosen = Math.max(0,(t.imageFiles || t.reviewImages).findIndex(i => (i.sha256 || i.url) === selectedAsset)); } showMedia(t,area); } else { const panel = node('div','','status-panel'); panel.append(node('span',latestResponse(t) ? workflowLabel(latestResponse(t)) : isBlocked(t) ? 'Owner action needed' : 'Current evidence','status-pill'), node('h2',t.status || 'In progress'), node('p',t.summary || 'Open the supporting material for this item.','detail-copy')); if (t.stages) { const stages = node('div','','stage-track'); for (const s of t.stages) stages.append(node('span',s.name,'stage ' + (['done','pass','complete'].includes(s.state) ? 'pass':''))); panel.append(stages); } area.append(panel); }
|
|
76
|
+
const record = latestResponse(t); if (record) { const r = node('div','','record'); r.append(node('strong',currentResponse(t) ? 'Your response is saved' : 'Your earlier response'),node('div',record.response.note),node('div',workflowLabel(record) + ' · ' + fmt(record.workflow?.atUTC || record.recordedAtUTC),'small')); area.prepend(r,responseProgress(record)); }
|
|
77
|
+
const evidence = node('div'); evidence.append(node('p',t.summary || 'No additional summary recorded.')); if(t.context) evidence.append(node('pre',t.context)); if (t.next) evidence.append(node('p','Next action: ' + t.next)); if (t.request?.text || t.originalRequest?.source) evidence.append(node('p','Original request: ' + (t.request?.text || t.originalRequest.source))); if (t.contributors?.length) { const ul = node('ul'); for (const c of t.contributors) ul.append(node('li',(data.agents?.[c.role]?.title || c.role) + ' — ' + c.contribution + ' (' + c.proof + ')')); evidence.append(details('Contributors',ul)); } if (t.evidence?.length) evidence.append(details('Technical evidence',node('pre',t.evidence.map(e => typeof e === 'string' ? e : JSON.stringify(e)).join('\n')))); area.append(details('Full context & evidence',evidence));
|
|
78
|
+
const inspector = node('aside','','inspector'); const owner = node('section'); const ownerRow = node('div','','owner'); const title = t.laneTitle || 'Topolo Engineering'; ownerRow.append(node('span',title.split(' ').map(x => x[0]).slice(0,2).join(''),'avatar')); const names = node('div'); names.append(node('div',title,'owner-name'),node('div','Prepared for you','small')); ownerRow.append(names); owner.append(ownerRow,node('p',t.owner || 'See originating lane for the next owner.')); inspector.append(owner);
|
|
79
|
+
const glance = node('section'); glance.append(node('h2',t.reviewImages?.length ? 'At a glance' : 'Next action'),node('p',t.reviewImages?.length ? (t.summary || t.status) : (t.next || 'No next action recorded.'))); if (t.verifiedAt || t.laneUpdatedAt) glance.append(node('p','Evidence · ' + fmt(t.verifiedAt || t.laneUpdatedAt),'small')); inspector.append(glance);
|
|
80
|
+
const support = node('section'); support.append(node('h2','Supporting material')); let count = 0; for (const link of t.links || []) { const a = anchor(link.label || 'Open source',link.url); if (a) {support.append(a); count++;} } if (t.roadmap?.url) { const a = anchor(t.roadmap.itemId ? 'Roadmap item' : 'Roadmap scope',t.roadmap.url); if(a) {support.append(a); count++;} } if (!count) support.append(node('p','No linked files for this item.')); const copy = node('button','Copy reference ↗','quiet'); copy.style.marginTop = '12px'; copy.onclick = () => copyReference(t); support.append(copy); inspector.append(support);
|
|
81
|
+
layout.append(area,inspector); content.append(layout); $('response-bar').hidden = false; $('response-context').textContent = t.reviewImages?.length && needsReview(t) ? 'Preference only. This does not approve implementation or publish anything.' : 'Send a scoped response. Track delivery, acceptance and completion in Your responses.'; $('primary-action').textContent = t.reviewImages?.length && needsReview(t) ? 'Choose this direction' : 'Send feedback'; $('primary-action').onclick = () => openResponse(t, t.reviewImages?.length && needsReview(t) ? 'preference':'feedback'); $('change-action').onclick = () => openResponse(t,'request_changes'); $('hold-action').onclick = () => openResponse(t,'hold_request');
|
|
82
|
+
}
|
|
83
|
+
function openResponse(t,intent) { responseItem = t; submissionId = crypto.randomUUID(); $('response-title').textContent = intent === 'preference' ? 'Record your preference' : intent === 'hold_request' ? 'Ask the owner to hold' : intent === 'request_changes' ? 'Request changes' : 'Send feedback'; $('response-scope').textContent = t.title; $('intent').value = intent; $('response-note').value = localStorage.getItem('topolo-studio-draft:' + t.id) || (intent === 'preference' ? 'My preferred direction is ' + (t.reviewImages[chosen]?.label || String(chosen + 1)) + '.' : ''); $('response-error').textContent = ''; $('response-dialog').showModal(); $('response-note').focus(); }
|
|
84
|
+
async function submitResponse(event) { event.preventDefault(); const t = responseItem; if (!t) return; const note = $('response-note').value.trim(); if (!note) return; const button = $('submit-response'); button.disabled = true; $('response-error').textContent = ''; const response = {intent:$('intent').value,note}; if (response.intent === 'preference' && t.reviewImages?.length) response.asset = t.imageFiles?.[chosen]?.sha256 || t.reviewImages[chosen].sha256 || t.reviewImages[chosen].url; const payload = {requestId:submissionId,itemId:t.id,revision:inbox.revisions[t.id],response}; if(t.nativeTask) { await submitNativeResponse(t,response,submissionId); return; } try { if (!inbox.nonce || !payload.revision) throw Error('Local decision inbox unavailable. Keep this draft and refresh.'); const res = await fetch('/api/decisions',{method:'POST',headers:{'Content-Type':'application/json','X-Studio-Token':inbox.nonce},body:JSON.stringify(payload)}); const record = await res.json(); if (!res.ok) throw Error(record.error || 'Response not confirmed saved.'); if (!inbox.responses.some(r => r.id === record.id)) inbox.responses.push(record); localStorage.removeItem('topolo-studio-draft:' + t.id); $('response-dialog').close(); view = 'recorded'; selected = t.id; render(); toast('Response saved. Track delivery and owner progress in Your responses.'); refresh(); } catch(e) { $('response-error').textContent = e.message; } finally {button.disabled = false;} }
|
|
85
|
+
function render() {
|
|
86
|
+
if (!data) return; for (const b of document.querySelectorAll('[data-view]')) b.setAttribute('aria-selected',String(b.dataset.view === view)); $('review-count').textContent = pendingDecisions().length; $('work-count').textContent = workItems().length; $('recorded-count').textContent = inbox.responses.length; $('updated').textContent = (data.activityConnection === 'live' ? 'Live activity · ' : 'Saved feed · ') + fmt(data.updatedAt); if (['releases','activity'].includes(view)) closeFinder(); $('response-bar').hidden = true; $('queue-toggle').hidden = ['releases','activity'].includes(view); $('search').placeholder = view === 'review' ? 'Find a decision…' : 'Find work…';
|
|
87
|
+
$('view-description').textContent = {review:'Decisions · Choices, approvals or information explicitly requested from you. Once you respond, track the result in Your responses.',work:'Work · Owner tasks, fixes, blockers and issue summaries. Items requesting your decision are in Decisions. This count is not the number of queued releases.',recorded:'Your responses · What you have answered, with delivery, acceptance and completion shown separately.',releases:'Releases · Saved environment evidence from the responsible owners.',activity:'Activity · Recorded changes across the connected feeds.'}[view];
|
|
88
|
+
$('decision-coverage').hidden = !['review','work'].includes(view) || data.decisionCoverage?.state !== 'incomplete';
|
|
89
|
+
$('decision-coverage').textContent = data.decisionCoverage?.summary || '';
|
|
90
|
+
renderReviewBacklog();
|
|
91
|
+
if (view === 'releases') { $('focus').replaceChildren(node('span','Delivery evidence','eyebrow'),node('h1','Release progress'),node('p','Saved evidence from the owning lanes. Each environment must be verified independently.','intro')); const wrap = node('div','','table-wrap'), table = node('table'), head = node('tr'); for(const label of ['Application','DEV','Staging','Production','Next']) head.append(node('th',label)); table.append(head); for(const r of data.releaseOverview || []) { const tr = node('tr'); for(const key of ['app','dev','staging','production','next']) tr.append(node('td',r[key] || 'Unverified')); table.append(tr); } wrap.append(table); $('focus').append(wrap); return; }
|
|
92
|
+
if (view === 'activity') { $('focus').replaceChildren(node('span',data.activityConnection === 'live' ? 'Live task timeline' : 'Saved timeline','eyebrow'),node('h1','Recent activity')); for(const c of (data.changes || []).slice(0,40)) { const row = node('article','','activity-row'); row.append(node('time',fmt(c.at)),node('div',c.title || 'Update'),node('p',c.detail || c.summary || '')); if(c.taskId) { const open = node('button','View task','secondary'); open.type='button'; open.onclick=()=>openTask(c.taskId); row.append(open); } $('focus').append(row); } return; }
|
|
93
|
+
const list = viewRows(); if (!list.some(t => t.id === selected)) selected = list[0]?.id || ''; $('queue-title').textContent = view === 'review' ? 'Find a decision' : view === 'recorded' ? 'Find a response' : 'Find work'; $('queue-toggle').textContent = view === 'review' ? 'Find decision' : view === 'recorded' ? 'Find response' : 'Find work'; renderPicker(); const t = list.find(t => t.id === selected); if(t) renderFocus(t); else { $('focus').replaceChildren(node('span',view === 'review' ? 'Published requests' : 'No matches','eyebrow'),node('h1',view === 'review' ? 'No published decisions waiting' : 'Nothing in this view'),node('p',view === 'review' ? 'No unanswered request matches this view. This does not establish that all work has been checked for missing decisions. Recorded responses stay in Your responses.' : 'Change the search or filters to see more work.','intro')); }
|
|
94
|
+
}
|
|
95
|
+
async function refresh() { if(refreshing) { refreshPending = true; return; } refreshing = true; try { let next = await savedJSON('updates.json'); next = await combineFeeds(next); let liveWork; try {liveWork = await savedJSON('/api/work');} catch {liveWork = {tasks:[],connection:'offline'};} window.nativeWork = liveWork; for (const task of liveWork.tasks || []) { const item = nativeTaskItem(task); if(task.state === 'completed') (next.completed ||= []).push(item); else (next.tasks ||= []).push(item); } let local; try {local = await savedJSON('/api/inbox');} catch {local = {responses:[],revisions:{}};} const fingerprint = JSON.stringify([next,local.responses]); data = next; inbox = local; if(initialHash) { const linked = allItems().find(t => t.id === initialHash); if(linked) { selected = linked.id; view = latestResponse(linked) ? 'recorded' : needsReview(linked) ? 'review' : 'work'; } initialHash = ''; } if(fingerprint !== lastFingerprint) { lastFingerprint = fingerprint; const value = $('lane').value; $('lane').replaceChildren(new Option('All lanes','')); const lanes = new Map(allItems().map(t => [t.laneId,t.laneTitle])); for(const [id,title] of lanes) if(id) $('lane').append(new Option(title || id,id)); $('lane').value = value; render(); } $('notice').hidden = false; $('notice').textContent = !inbox.nonce ? 'Viewer only: local response inbox is unavailable. Copy a reference to keep feedback attached to the right work.' : data.sourceNotice?.includes('unavailable') ? data.sourceNotice : liveWork.activityNotice ? liveWork.activityNotice : liveWork.connection === 'live' ? 'Task activity is live. Earlier saved-feedback delivery remains separate; follow each response for its acceptance and result.' : 'Native execution is disconnected. Saved task evidence is shown; no work is being retried.'; $('notice').hidden = !$('notice').textContent; } catch { $('notice').hidden = false; $('notice').textContent = 'Saved feeds unavailable. This view may be historical; no current status is inferred.'; } finally {refreshing = false; if(refreshPending) {refreshPending = false; void refresh();}} }
|
|
96
|
+
const viewButtons = [...document.querySelectorAll('[data-view]')]; for(const [index,b] of viewButtons.entries()) { b.onclick = () => setView(b.dataset.view); b.onkeydown = e => { const target = e.key === 'ArrowRight' ? (index+1)%viewButtons.length : e.key === 'ArrowLeft' ? (index+viewButtons.length-1)%viewButtons.length : e.key === 'Home' ? 0 : e.key === 'End' ? viewButtons.length-1 : null; if(target !== null) {e.preventDefault();viewButtons[target].focus();viewButtons[target].click();} }; }
|
|
97
|
+
for(const id of ['search','lane','state']) $(id).oninput = renderPicker;
|
|
98
|
+
$('refresh').onclick = refresh; $('queue-toggle').onclick = openFinder; $('close-queue').onclick = closeFinder; $('queue').addEventListener('close', () => $('queue-toggle').setAttribute('aria-expanded','false')); $('queue').addEventListener('keydown', e => { if(e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); closeFinder(); } });
|
|
99
|
+
$('response-form').onsubmit = submitResponse; $('response-note').oninput = () => { if(responseItem) localStorage.setItem('topolo-studio-draft:' + responseItem.id,$('response-note').value); };
|
|
100
|
+
for(const b of document.querySelectorAll('[data-close]')) b.onclick = () => $(b.dataset.close).close();
|
|
101
|
+
document.addEventListener('keydown', e => { if(e.target.matches('input,textarea,select') || document.querySelector('dialog[open]')) return; if(['ArrowLeft','ArrowRight'].includes(e.key) && data && !['activity','releases'].includes(view)) { const list = viewRows(), i = list.findIndex(t => t.id === selected), target = i + (e.key === 'ArrowRight' ? 1:-1); if(list[target]) {e.preventDefault();selectItem(list[target]);} } });
|
|
102
|
+
if(initialHash) selected = initialHash;
|
|
103
|
+
refresh(); document.addEventListener('visibilitychange',() => {if(!document.hidden) refresh();});
|
|
104
|
+
|
|
105
|
+
// Native task updates are pushed from the owning connection; no polling loop.
|
|
106
|
+
function nativeTaskItem(task) {
|
|
107
|
+
const workers=(window.nativeWork?.workers || []).filter(w=>w.taskId===task.id);
|
|
108
|
+
const latest=workers[workers.length-1], choice=latest?.resolution?.resolved;
|
|
109
|
+
return {id:'task:'+task.id,nativeTask:task,originalRequest:{source:task.objective},kind:'task',title:({'pending-work-discovery':'Pending work review','retained-inventory-verification':'Work inventory check'})[task.route?.task?.type] || (({engineering:'Engineering',operations:'Operations',cicd:'CI/CD'})[task.channel] || 'Shared')+' task',application:({engineering:'Engineering',operations:'Operations',cicd:'CI/CD'})[task.channel] || task.channel,laneId:'native-work',laneTitle:'Task coordinators',owner:task.deliveryOwner,status:task.state,statusLabel:task.state.replaceAll('_',' '),summary:task.summary,next:task.verification?.summary || task.route?.acceptance || 'The coordinator will select the appropriate discipline.',needsHuman:task.state==='needs_input',closed:task.state==='completed',blocked:['blocked','unknown','failed'].includes(task.state),evidenceAtUTC:new Date(task.updatedAt*1000).toISOString(),evidence:(task.checkpoints || []).map(p=>p.path),context:['Task: '+task.id,'Phase: '+task.phase,choice?'Execution: '+choice.model+' / '+choice.effort:'Routing pending','Specialists are replaced after each task; progress and results remain here.',task.result?.summary || ''].join('\n\n'),links:[]};
|
|
110
|
+
}
|
|
111
|
+
let workRequestId;
|
|
112
|
+
$('request-work').onclick=()=>{workRequestId=localStorage.getItem('topolo-work-request-id') || crypto.randomUUID();localStorage.setItem('topolo-work-request-id',workRequestId);$('work-objective').value=localStorage.getItem('topolo-work-draft') || '';$('work-error').textContent='';$('work-dialog').showModal();$('work-objective').focus();};
|
|
113
|
+
$('work-objective').oninput=()=>localStorage.setItem('topolo-work-draft',$('work-objective').value);
|
|
114
|
+
$('work-form').onsubmit=async event=>{event.preventDefault();$('work-submit').disabled=true;try{if(!window.nativeWork?.nonce || window.nativeWork.connection!=='live')throw Error('The native coordinator is disconnected. Your draft is retained.');const response=await fetch('/api/work',{method:'POST',headers:{'Content-Type':'application/json','X-Studio-Token':window.nativeWork.nonce},body:JSON.stringify({requestId:workRequestId,objective:$('work-objective').value.trim(),channel:$('work-channel').value,canonicalClaimId:null})});const result=await response.json();if(!response.ok)throw Error(result.error || 'Dispatch was not confirmed. Keep the same request identity.');localStorage.removeItem('topolo-work-request-id');localStorage.removeItem('topolo-work-draft');$('work-dialog').close();view='work';selected='task:'+result.task.id;await refresh();toast('Request recorded. Follow its progress in Work.');}catch(error){$('work-error').textContent=error.message;}finally{$('work-submit').disabled=false;}};
|
|
115
|
+
const workEvents=new EventSource('/api/work/events');workEvents.onmessage=()=>refresh();
|
|
116
|
+
const feedEvents=new EventSource('/api/feed/events');feedEvents.onmessage=()=>refresh();
|
|
117
|
+
workEvents.onerror=()=>{if(window.nativeWork)window.nativeWork.connection='offline';};
|
|
118
|
+
|
|
119
|
+
async function submitNativeResponse(item,response,requestId) {
|
|
120
|
+
try {const result=await fetch('/api/work/responses',{method:'POST',headers:{'Content-Type':'application/json','X-Studio-Token':window.nativeWork.nonce},body:JSON.stringify({requestId,taskId:item.nativeTask.id,revision:item.nativeTask.revision,intent:response.intent==='hold_request'?'hold':response.intent==='request_changes'?'request_changes':'continue',note:response.note})});const value=await result.json();if(!result.ok)throw Error(value.error || 'Response not accepted.');$('response-dialog').close();localStorage.removeItem('topolo-studio-draft:'+item.id);view='work';await refresh();toast('Response recorded on the task. Its next attempt uses fresh workers.');}catch(error){$('response-error').textContent=error.message;}finally{$('submit-response').disabled=false;}
|
|
121
|
+
}
|