codehere 0.7.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +22 -13
  2. package/dist/application/services/verdict-service.js +154 -0
  3. package/dist/application/use-cases/assess-cross-vendor.js +126 -0
  4. package/dist/application/use-cases/build-cross-vendor-verdict.js +118 -0
  5. package/dist/application/use-cases/export-aat.js +107 -0
  6. package/dist/application/use-cases/generate-acceptance-criteria.js +223 -0
  7. package/dist/application/use-cases/generate-final-report.js +140 -15
  8. package/dist/application/use-cases/generate-narration.js +5 -3
  9. package/dist/application/use-cases/generate-thread-title.js +5 -2
  10. package/dist/application/use-cases/narrator-prompt-template.js +1 -1
  11. package/dist/application/use-cases/plan-auto-review.js +57 -0
  12. package/dist/application/use-cases/reconcile-audit.js +100 -0
  13. package/dist/application/use-cases/rerun-tests.js +226 -0
  14. package/dist/application/use-cases/search-sessions.js +70 -0
  15. package/dist/application/use-cases/should-auto-review.js +71 -0
  16. package/dist/application/use-cases/thread-export.js +90 -0
  17. package/dist/application/use-cases/verify-claim.js +187 -5
  18. package/dist/domain/audit.js +11 -0
  19. package/dist/domain/services/codebase-detector.js +0 -6
  20. package/dist/domain/task.js +91 -0
  21. package/dist/error-handler.js +3 -115
  22. package/dist/index.js +279 -31
  23. package/dist/infrastructure/completion/bash-completion.js +7 -3
  24. package/dist/infrastructure/completion/fish-completion.js +7 -12
  25. package/dist/infrastructure/completion/zsh-completion.js +7 -12
  26. package/dist/infrastructure/config/config-manager.js +4 -3
  27. package/dist/infrastructure/config/product-context.js +1 -19
  28. package/dist/infrastructure/context/adapters/figma-source.js +37 -2
  29. package/dist/infrastructure/context/adapters/github-issue-source.js +3 -2
  30. package/dist/infrastructure/context/github-api.js +110 -0
  31. package/dist/infrastructure/context/github-device-flow.js +65 -0
  32. package/dist/infrastructure/context/oauth-store.js +5 -2
  33. package/dist/infrastructure/context/registry-bootstrap.js +0 -4
  34. package/dist/infrastructure/cost/budget-config.js +4 -3
  35. package/dist/infrastructure/cost/cost-router.js +16 -23
  36. package/dist/infrastructure/cost/spend-aggregator.js +91 -16
  37. package/dist/infrastructure/cost/team-ledger.js +355 -0
  38. package/dist/infrastructure/discovery/discovery-log.js +85 -0
  39. package/dist/infrastructure/hooks/audit-tip-signature.js +336 -0
  40. package/dist/infrastructure/hooks/claude-sdk-hook.js +139 -3
  41. package/dist/infrastructure/hooks/hook-mode.js +20 -4
  42. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +8 -2
  43. package/dist/infrastructure/hooks/pre-prompt-scan.js +3 -3
  44. package/dist/infrastructure/observability/execution-tracer.js +1 -1
  45. package/dist/infrastructure/orchestration/agents-config-seed.js +23 -11
  46. package/dist/infrastructure/orchestration/aider-adapter.js +188 -24
  47. package/dist/infrastructure/orchestration/artifacts-extractor.js +60 -2
  48. package/dist/infrastructure/orchestration/cassette-recorder.js +1 -1
  49. package/dist/infrastructure/orchestration/claude-code-adapter.js +117 -3
  50. package/dist/infrastructure/orchestration/codex-adapter.js +52 -16
  51. package/dist/infrastructure/orchestration/conductor-adapter.js +46 -7
  52. package/dist/infrastructure/orchestration/context-handoff.js +32 -14
  53. package/dist/infrastructure/orchestration/detect-ollama.js +75 -0
  54. package/dist/infrastructure/orchestration/failover-config.js +1 -11
  55. package/dist/infrastructure/orchestration/gemini-adapter.js +92 -12
  56. package/dist/infrastructure/orchestration/generic-cli-adapter.js +134 -3
  57. package/dist/infrastructure/orchestration/graphify-context.js +14 -59
  58. package/dist/infrastructure/orchestration/memory-injector.js +4 -1
  59. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +167 -9
  60. package/dist/infrastructure/orchestration/opencode-adapter.js +84 -14
  61. package/dist/infrastructure/orchestration/progress-aggregator.js +275 -23
  62. package/dist/infrastructure/orchestration/session-manager.js +593 -94
  63. package/dist/infrastructure/orchestration/session-persistence.js +177 -41
  64. package/dist/infrastructure/orchestration/thread-key-store.js +1 -1
  65. package/dist/infrastructure/orchestration/write-pass-store.js +0 -8
  66. package/dist/infrastructure/protocols/cloud-sync.js +2 -2
  67. package/dist/infrastructure/protocols/mcp-helpers.js +1 -1
  68. package/dist/infrastructure/protocols/mcp-server-impl.js +1498 -354
  69. package/dist/infrastructure/protocols/mcp-tools.js +401 -0
  70. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +121 -0
  71. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +44 -0
  72. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +56 -0
  73. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +588 -0
  74. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +168 -0
  75. package/dist/infrastructure/protocols/ui/app-script-conversation.js +67 -0
  76. package/dist/infrastructure/protocols/ui/app-script-drawer.js +115 -0
  77. package/dist/infrastructure/protocols/ui/app-script-failover.js +133 -0
  78. package/dist/infrastructure/protocols/ui/app-script-github.js +113 -0
  79. package/dist/infrastructure/protocols/ui/app-script-observer.js +104 -0
  80. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +80 -0
  81. package/dist/infrastructure/protocols/ui/app-script-overlay.js +203 -0
  82. package/dist/infrastructure/protocols/ui/app-script-recovery.js +136 -0
  83. package/dist/infrastructure/protocols/ui/app-script-render.js +166 -0
  84. package/dist/infrastructure/protocols/ui/app-script-share.js +78 -0
  85. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +194 -0
  86. package/dist/infrastructure/protocols/ui/app-script-tour.js +78 -0
  87. package/dist/infrastructure/protocols/ui/app-script-wpass.js +312 -0
  88. package/dist/infrastructure/protocols/ui/app-script.js +2998 -0
  89. package/dist/infrastructure/protocols/ui/font-faces.js +7 -0
  90. package/dist/infrastructure/protocols/ui/styles.js +2147 -0
  91. package/dist/infrastructure/protocols/ui/tokens.js +96 -0
  92. package/dist/infrastructure/protocols/ui-page-v2.js +116 -4126
  93. package/dist/infrastructure/protocols/verify-page.js +884 -148
  94. package/dist/infrastructure/security/fix-code.js +144 -0
  95. package/dist/infrastructure/security/fix-secret.js +89 -0
  96. package/dist/infrastructure/security/permissions-config.js +3 -2
  97. package/dist/infrastructure/security/scan-types.js +10 -0
  98. package/dist/infrastructure/security/ship-scan.js +195 -0
  99. package/dist/infrastructure/security/trust-scorer.js +70 -3
  100. package/dist/infrastructure/security/vibe-checks.js +99 -0
  101. package/dist/infrastructure/storage/atomic-write.js +86 -0
  102. package/dist/infrastructure/storage/project-thread-mirror.js +151 -4
  103. package/dist/infrastructure/storage/project-thread-reader.js +262 -25
  104. package/dist/infrastructure/storage/project-thread-title-backfill.js +1 -1
  105. package/dist/monitoring.js +2 -2
  106. package/dist/presentation/cli/commands/audit-command.js +79 -4
  107. package/dist/presentation/cli/commands/budget-command.js +2 -2
  108. package/dist/presentation/cli/commands/check-command.js +96 -0
  109. package/dist/presentation/cli/commands/context-source-command.js +20 -1
  110. package/dist/presentation/cli/commands/help-command.js +5 -5
  111. package/dist/presentation/cli/commands/hook-command.js +5 -5
  112. package/dist/presentation/cli/commands/init-command.js +89 -66
  113. package/dist/presentation/cli/commands/memory-command.js +28 -28
  114. package/dist/presentation/cli/commands/permission-command.js +1 -12
  115. package/dist/presentation/cli/commands/protocols-builder.js +2 -2
  116. package/dist/presentation/cli/commands/protocols-command.js +4 -4
  117. package/dist/presentation/cli/commands/repo-map-builder.js +1 -1
  118. package/dist/presentation/cli/commands/report-command.js +48 -32
  119. package/dist/presentation/cli/commands/scan-command.js +52 -0
  120. package/dist/presentation/cli/commands/scan-tool-command.js +24 -8
  121. package/dist/presentation/cli/commands/search-command.js +51 -0
  122. package/dist/presentation/cli/commands/setup-command.js +31 -8
  123. package/dist/presentation/cli/commands/status-command.js +39 -86
  124. package/dist/presentation/cli/commands/team-command.js +256 -0
  125. package/dist/presentation/cli/commands/thread-command.js +87 -5
  126. package/dist/presentation/cli/commands/trust-command.js +53 -31
  127. package/dist/presentation/cli/commands/verify-command.js +74 -9
  128. package/dist/presentation/cli/error-display.js +0 -8
  129. package/dist/presentation/cli/repl.js +15 -7
  130. package/dist/presentation/cli/task-deliverable.js +36 -10
  131. package/dist/testing/replay-adapter.js +0 -19
  132. package/dist/ui.js +51 -239
  133. package/dist/utils/version.js +0 -6
  134. package/package.json +30 -10
  135. package/dist/domain/entities/task.js +0 -61
  136. package/dist/infrastructure/protocols/ui-page.js +0 -2377
  137. package/dist/infrastructure/security/input-validator.js +0 -152
  138. package/dist/infrastructure/storage/embeddings-path.js +0 -37
  139. package/dist/infrastructure/storage/task-repository.js +0 -206
@@ -0,0 +1,166 @@
1
+ // Pure render helpers extracted from app-script.ts (god-file split 2/2, 2026-06-29).
2
+ // A browser-JS string fragment interpolated into APP_SCRIPT at the same scope —
3
+ // function declarations hoist exactly as before, so no behavior change.
4
+ export const RENDER_HELPERS = `
5
+ function costPill(cents, paid) {
6
+ if (cents === 0 || cents === null || cents === undefined) {
7
+ return '<span class="cost-signal free">$0.00</span>';
8
+ }
9
+ const dollars = '$' + (cents/100).toFixed(cents < 10 ? 4 : 2);
10
+ return '<span class="cost-signal ' + (paid === false ? 'free' : 'paid') + '">' + dollars + '</span>';
11
+ }
12
+ // Inline code-card renderer · single-pass tokenizer · processes one token at
13
+ // a time so substitution markup never gets re-matched on the next replace.
14
+ // Lightweight intentionally · "looks like code", not a real lexer.
15
+ function codeCard(path, snippet, lang) {
16
+ const ext = (lang || path.split('.').pop() || '').toLowerCase();
17
+ const langLabel = ext === 'ts' || ext === 'tsx' ? 'typescript'
18
+ : ext === 'js' || ext === 'jsx' ? 'javascript'
19
+ : ext === 'py' ? 'python'
20
+ : ext === 'rs' ? 'rust'
21
+ : ext === 'go' ? 'go'
22
+ : ext === 'lua' ? 'lua'
23
+ : (ext || 'text');
24
+ function esc(s){ return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
25
+ const KW = /^(const|let|var|function|return|if|else|for|while|import|export|from|class|extends|new|await|async|throw|try|catch|finally|of|in|typeof|null|true|false|this|interface|type|public|private|readonly|static|enum|as)\\b/;
26
+ const TP = /^(string|number|boolean|void|any|unknown|Promise|Array|Record|Map|Set|Request|Response|NextFunction)\\b/;
27
+ // Priority alternation · line-comment / single-quote string / double-quote string / number / identifier.
28
+ const TOKEN = /^(\\/\\/[^\\n]*|'(?:\\\\.|[^'\\\\])*'|"(?:\\\\.|[^"\\\\])*"|\\d+(?:\\.\\d+)?|[A-Za-z_$][A-Za-z0-9_$]*)/;
29
+ function tokenize(line){
30
+ let out = '', i = 0;
31
+ while(i < line.length){
32
+ const rest = line.slice(i);
33
+ const m = TOKEN.exec(rest);
34
+ if(!m){ out += esc(rest[0]); i += 1; continue; }
35
+ const tok = m[0];
36
+ if(tok.charAt(0)==='/') out += '<span class="cm">'+esc(tok)+'</span>';
37
+ else if(tok.charAt(0)==='"' || tok.charAt(0)==='\\'') out += '<span class="str">'+esc(tok)+'</span>';
38
+ else if(/^\\d/.test(tok)) out += '<span class="num">'+esc(tok)+'</span>';
39
+ else if(KW.test(tok)) out += '<span class="kw">'+esc(tok)+'</span>';
40
+ else if(TP.test(tok)) out += '<span class="tp">'+esc(tok)+'</span>';
41
+ else if(line.charAt(i + tok.length) === '(') out += '<span class="fn">'+esc(tok)+'</span>';
42
+ else out += esc(tok);
43
+ i += tok.length;
44
+ }
45
+ return out;
46
+ }
47
+ const lines = String(snippet || '').split('\\n');
48
+ const body = lines.map(function(raw, i){
49
+ return '<span class="ln">'+String(i+1).padStart(2,' ')+'</span>'+tokenize(raw);
50
+ }).join('\\n');
51
+ const meta = lines.length + (lines.length===1?' line':' lines');
52
+ return '<div class="code-card">'
53
+ + '<div class="head"><span class="lang">'+langLabel+'</span>'
54
+ + '<span class="path">'+esc(path)+'</span>'
55
+ + '<span class="meta">'+meta+'</span></div>'
56
+ + '<pre>'+body+'</pre></div>';
57
+ }
58
+ // Markdown renderer for agent prose (Final Report + say steps). The report used
59
+ // to escape raw text, so fenced code blocks, inline code, bold and numbered
60
+ // lists all leaked as literal characters — the "code not in a proper block"
61
+ // complaint. Reuses codeCard for fenced blocks. Fence/backtick chars are built
62
+ // from charCode 96 because this whole file is a backtick template literal.
63
+ function mdInline(s){
64
+ var BT=String.fromCharCode(96);
65
+ s=s.replace(/\\*\\*([^*]+)\\*\\*/g,'<strong>$1</strong>');
66
+ var parts=s.split(BT);
67
+ if(parts.length>1 && parts.length%2===1){
68
+ var out='';
69
+ for(var i=0;i<parts.length;i++){ out+=(i%2===1)?'<code class="ic">'+parts[i]+'</code>':parts[i]; }
70
+ s=out;
71
+ }
72
+ return s;
73
+ }
74
+ function mdProse(t){
75
+ var esc=String(t==null?'':t).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
76
+ var blocks=esc.split(/\\n\\s*\\n/).map(function(b){return b.replace(/^\\n+|\\n+$/g,'').trim();}).filter(Boolean);
77
+ function cells(l){ return l.trim().replace(/^\\|/,'').replace(/\\|$/,'').split('|').map(function(c){return mdInline(c.trim());}); }
78
+ return blocks.map(function(b){
79
+ var lines=b.split('\\n');
80
+ // ATX header (# .. ######): emit the heading, render any trailing lines as prose
81
+ var hm=lines[0].match(/^(#{1,6})\\s+(.*)$/);
82
+ if(hm){
83
+ var lvl=Math.min(hm[1].length+1,6);
84
+ var rest=lines.slice(1).join('\\n').trim();
85
+ return '<h'+lvl+' class="rep-h">'+mdInline(hm[2])+'</h'+lvl+'>'+(rest?mdProse(rest):'');
86
+ }
87
+ // pipe table: every line starts with |, line 2 is the |---|---| separator
88
+ if(lines.length>=2 && lines.every(function(l){return /^\\s*\\|/.test(l);}) && /^[\\s|:-]+$/.test(lines[1]) && lines[1].indexOf('-')!==-1){
89
+ var thead='<tr>'+cells(lines[0]).map(function(c){return '<th>'+c+'</th>';}).join('')+'</tr>';
90
+ var tbody=lines.slice(2).map(function(l){return '<tr>'+cells(l).map(function(c){return '<td>'+c+'</td>';}).join('')+'</tr>';}).join('');
91
+ return '<table class="rep-table"><thead>'+thead+'</thead><tbody>'+tbody+'</tbody></table>';
92
+ }
93
+ var isList=lines.length>0 && lines.every(function(l){return /^\\s*(\\d+\\.|[-*])\\s+/.test(l);});
94
+ if(isList){
95
+ var ordered=/^\\s*\\d+\\./.test(lines[0]);
96
+ var items=lines.map(function(l){return '<li>'+mdInline(l.replace(/^\\s*(\\d+\\.|[-*])\\s+/,''))+'</li>';}).join('');
97
+ return (ordered?'<ol':'<ul')+' class="rep-list">'+items+(ordered?'</ol>':'</ul>');
98
+ }
99
+ return '<p class="rep-p">'+mdInline(lines.join('<br>'))+'</p>';
100
+ }).join('');
101
+ }
102
+ function renderReportMd(raw){
103
+ var BT=String.fromCharCode(96), FENCE=BT+BT+BT;
104
+ var segs=String(raw==null?'':raw).split(FENCE);
105
+ var html='';
106
+ for(var i=0;i<segs.length;i++){
107
+ if(i%2===1){
108
+ var lines=segs[i].split('\\n');
109
+ var lang='';
110
+ if(lines.length && /^[a-zA-Z]{1,12}$/.test(lines[0].trim())){ lang=lines[0].trim(); lines=lines.slice(1); }
111
+ var codeBody=lines.join('\\n').replace(/^\\n+|\\n+$/g,'');
112
+ html+=codeCard('', codeBody, lang);
113
+ } else if(segs[i].trim()){
114
+ html+=mdProse(segs[i]);
115
+ }
116
+ }
117
+ return html||mdProse(String(raw||''));
118
+ }
119
+ // Compact one-line label for the step trace — drops fenced code (shown in full
120
+ // in the conversation), collapses whitespace, escapes, renders inline md, and
121
+ // truncates. Was dumping the whole raw say (incl. literal fences, unescaped).
122
+ function compactLabel(s){
123
+ var BT=String.fromCharCode(96);
124
+ var t=String(s==null?'':s).split(BT+BT+BT).filter(function(_,i){return i%2===0;}).join(' ');
125
+ t=t.replace(/\\s+/g,' ').trim();
126
+ var esc=t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
127
+ if(esc.length>140) esc=esc.slice(0,140)+'\\u2026';
128
+ return mdInline(esc);
129
+ }
130
+ // ANSI -> HTML: render the SGR colors a real terminal shows (npm test, git,
131
+ // stack traces) instead of leaking raw escape codes as garbage. Stateless,
132
+ // hand-rolled, 16 colors + bold/dim/underline — NOT a terminal emulator. ESC is
133
+ // charCode 27; regexes built via new RegExp to keep the escaping sane.
134
+ function ansiToHtml(raw){
135
+ var ESC=String.fromCharCode(27);
136
+ var s=String(raw==null?'':raw);
137
+ // drop non-SGR CSI (cursor moves / clears) + other ESC seqs so they don't leak
138
+ s=s.replace(new RegExp(ESC+'\\\\[[0-9;?]*[A-LN-Za-ln-z]','g'),'').replace(new RegExp(ESC+'[^\\\\[]','g'),'');
139
+ var COLORS={'30':'#5c6370','31':'#e06c75','32':'#98c379','33':'#e5c07b','34':'#61afef','35':'#c678dd','36':'#56b6c2','37':'#abb2bf','90':'#7f848e','91':'#e06c75','92':'#98c379','93':'#e5c07b','94':'#61afef','95':'#c678dd','96':'#56b6c2','97':'#ffffff'};
140
+ function esc(t){ return t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
141
+ var cur={};
142
+ function open(){ var st=''; if(cur.color)st+='color:'+cur.color+';'; if(cur.bold)st+='font-weight:600;'; if(cur.dim)st+='opacity:.6;'; if(cur.underline)st+='text-decoration:underline;'; return st?'<span style="'+st+'">':''; }
143
+ var re=new RegExp(ESC+'\\\\[([0-9;]*)m','g');
144
+ var out='', last=0, m;
145
+ function emit(text){ if(!text)return; var o=open(); out+= o ? o+esc(text)+'</span>' : esc(text); }
146
+ while((m=re.exec(s))!==null){
147
+ emit(s.slice(last,m.index)); last=re.lastIndex;
148
+ var codes=m[1]?m[1].split(';'):['0'];
149
+ for(var i=0;i<codes.length;i++){ var c=codes[i]||'0'; if(c==='0'||c==='')cur={}; else if(c==='1')cur.bold=true; else if(c==='2')cur.dim=true; else if(c==='4')cur.underline=true; else if(c==='22'){cur.bold=false;cur.dim=false;} else if(c==='39')cur.color=null; else if(COLORS[c])cur.color=COLORS[c]; }
150
+ }
151
+ emit(s.slice(last));
152
+ return out;
153
+ }
154
+ // Render a tool step's output: ANSI-colored preview inline; if the full output
155
+ // is longer, a <details> expander reveals the whole thing (test runs, diffs,
156
+ // stack traces were silently clipped to 400 chars before).
157
+ function renderStepOutput(z){
158
+ var prev=z.output!=null?String(z.output):'';
159
+ var full=z.fullOutput!=null?String(z.fullOutput):prev;
160
+ if(!prev && !full) return '';
161
+ if(full.length<=prev.length+2) return '<div class="tl-out" data-user-content>'+ansiToHtml(prev)+'</div>';
162
+ return '<details class="tl-out-x"><summary><span class="tl-caret">\\u25B8</span><span class="tl-out tl-out-prev" data-user-content>'+ansiToHtml(prev)+'</span><span class="tl-out-more">show full output</span></summary>'
163
+ +'<pre class="tl-out-full" data-user-content>'+ansiToHtml(full)+'</pre></details>';
164
+ }
165
+ `;
166
+ //# sourceMappingURL=app-script-render.js.map
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Share-thread modal wire-up — turning a local thread into something that can be handed to
3
+ * someone else, and the controls around that.
4
+ *
5
+ * Split out of app-script.ts (strangler fig). Verbatim slice, interpolated back at the
6
+ * position it was cut from so it keeps sharing the surrounding IIFE scope — its functions
7
+ * are still called from the code left behind. Verified by hashing the composed APP_SCRIPT
8
+ * before and after: same bytes.
9
+ */
10
+ export const SHARE_MODAL = ` /* v0.8 S4 · share-thread modal wire-up */
11
+ let _shareBundle = null;
12
+ let _shareLink = '';
13
+ const shareModal = document.getElementById('share-modal');
14
+ const openShare = async ()=>{
15
+ // Fix D10 (2026-05-23) · Two bugs in one:
16
+ // (a) _detail is a STRING sessionId, not an object · _detail.sessionId
17
+ // would have been undefined even when a session WAS selected.
18
+ // (b) No session selected previously threw a useless toast · now we
19
+ // auto-pick the most-recent completed session. The user can still
20
+ // open the modal from any task by clicking it first.
21
+ let sessionId = _detail;
22
+ if(!sessionId){
23
+ const all = allS(_cached.tasks||{});
24
+ const done = all.filter(x=>x.status==='completed'||x.status==='done');
25
+ if(!done.length){
26
+ flashToast('No completed sessions yet · run a task first');
27
+ return;
28
+ }
29
+ // Most recent done (sort by completedAt desc)
30
+ done.sort((a,b)=>(b.completedAt||0)-(a.completedAt||0));
31
+ sessionId = done[0].sessionId;
32
+ }
33
+ shareModal.classList.add('open');
34
+ document.getElementById('share-link-id').textContent = '…fetching…';
35
+ document.getElementById('share-link-fp').textContent = '…';
36
+ try{
37
+ const r = await fetch('/api/thread/' + encodeURIComponent(sessionId) + '/export', { method:'POST' });
38
+ if(!r.ok) throw new Error('export failed · HTTP ' + r.status);
39
+ const j = await r.json();
40
+ _shareBundle = j.bundle;
41
+ _shareLink = j.link;
42
+ document.getElementById('share-link-id').textContent = (j.bundle?.body?.sessionId || sessionId || '').slice(0,12);
43
+ document.getElementById('share-link-fp').textContent = (j.fingerprint || '').replace('...', '…');
44
+ }catch(err){
45
+ document.getElementById('share-link-id').textContent = 'error';
46
+ document.getElementById('share-link-fp').textContent = (err && err.message) || 'see console';
47
+ console.error('share export failed', err);
48
+ }
49
+ };
50
+ const closeShare = ()=>shareModal.classList.remove('open');
51
+ // S13 · the share button left the header — the ⌘K "Share this thread"
52
+ // command invokes this same opener (existing modal, existing export).
53
+ window.openShare = openShare;
54
+ document.getElementById('share-close')?.addEventListener('click', closeShare);
55
+ shareModal?.addEventListener('click', e=>{ if(e.target===shareModal) closeShare(); });
56
+ document.getElementById('share-copy-link')?.addEventListener('click', async ()=>{
57
+ if(!_shareLink) return;
58
+ try{ await navigator.clipboard.writeText(_shareLink); flashToast('Link copied'); }
59
+ catch{ flashToast('Could not copy · try the .json download'); }
60
+ });
61
+ document.getElementById('share-download')?.addEventListener('click', ()=>{
62
+ if(!_shareBundle) return;
63
+ const blob = new Blob([JSON.stringify(_shareBundle, null, 2)], { type:'application/json' });
64
+ const url = URL.createObjectURL(blob);
65
+ const a = document.createElement('a');
66
+ a.href = url;
67
+ a.download = (_shareBundle.body?.sessionId?.slice(0,12) || 'thread') + '.codehere.json';
68
+ document.body.appendChild(a); a.click(); a.remove();
69
+ setTimeout(()=>URL.revokeObjectURL(url), 1000);
70
+ });
71
+ function flashToast(msg){
72
+ const t = document.getElementById('share-toast');
73
+ if(!t) return;
74
+ t.textContent = msg; t.classList.add('show');
75
+ setTimeout(()=>t.classList.remove('show'), 1600);
76
+ }
77
+ `;
78
+ //# sourceMappingURL=app-script-share.js.map
@@ -0,0 +1,194 @@
1
+ // The project sidebar — the rail that IS the navigator. Extracted from
2
+ // app-script.ts (strangler-fig split 4, 2026-08-08; same pattern as
3
+ // app-script-render.ts and app-script-conversation.ts). A browser-JS string
4
+ // fragment interpolated into APP_SCRIPT at the same scope: function
5
+ // declarations hoist exactly as before, so this is a MOVE, not a rewrite —
6
+ // proven by the built APP_SCRIPT hashing identically before and after.
7
+ //
8
+ // Owns: sidebar state (_sbExpanded/_sbThreads/_sbQuery), per-project thread
9
+ // leaves (sbLeavesHtml), the team roster (loadTeam/teamSectionHtml — where the
10
+ // per-agent rainbow was killed, DESIGN.md §5), the nav section, renderSidebar
11
+ // itself, and the row/leaf click handlers.
12
+ //
13
+ // House rules for this file (it is INSIDE a TS template literal when served):
14
+ // no literal backticks, unicode escapes stay double-escaped, served regexes
15
+ // double-escaped. The opening backtick hugs the first line and the closing one
16
+ // hugs the last ON PURPOSE: a stray newline either side changes the served
17
+ // bytes, which the identity check catches.
18
+ export const SIDEBAR = `var _sbExpanded = {}; // slug -> true (user-toggled expansion)
19
+ var _sbThreads = {}; // slug -> tasks[] (lazy-loaded per project; undefined = not fetched)
20
+ var _sbQuery = {}; // slug -> filter string (the per-project thread search)
21
+ function _sbEsc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];});}
22
+ var SB_SHOW=8; // recent threads shown per project before "view all"; the rest live in the room
23
+ function sbLeavesHtml(tasks, cwd, slug, query){
24
+ if(tasks===null||tasks===undefined) return '<div class="sb-loading">loading\\u2026</div>';
25
+ // A "thread" is a real top-level task: not a reply turn (parentSessionId), not a
26
+ // review, not archived/demo, and not a failover-lineage marker.
27
+ var all=tasks.filter(function(t){
28
+ if(!t || t.archived || t.parentSessionId) return false;
29
+ if(/^demo-/.test(t.sessionId||'')) return false;
30
+ var p=t.prompt||''; if(p.indexOf('Switched from')>=0 && p.indexOf('rate limit')>=0) return false;
31
+ return true;
32
+ }).sort(function(a,b){return (b.startedAt||0)-(a.startedAt||0);});
33
+ var enc=encodeURIComponent(cwd), q=(query||'').trim().toLowerCase(), h='';
34
+ // Search box once a project has more threads than fit — so a big project is
35
+ // navigable by typing, never an endless scroll.
36
+ if(all.length>SB_SHOW || q){
37
+ h+='<div class="sb-search"><input type="text" class="sb-q" placeholder="Filter '+all.length+' threads\\u2026" value="'+_sbEsc(query||'')+'" oninput="sbSearch(\\''+_sbEsc(slug)+'\\',\\''+enc+'\\',this.value)" onclick="event.stopPropagation()" onkeydown="event.stopPropagation()"></div>';
38
+ }
39
+ if(!all.length) return h+'<div class="sb-loading">no tasks yet</div>';
40
+ var matched=q?all.filter(function(t){return (t.prompt||'').toLowerCase().indexOf(q)>=0;}):all;
41
+ if(!matched.length) return h+'<div class="sb-loading">no match</div>';
42
+ var cap=matched.slice(0, q?40:SB_SHOW);
43
+ cap.forEach(function(t){
44
+ var sel=(_detail===t.sessionId); var k=agentKey(t.agent);
45
+ h+='<div class="sb-task'+(sel?' active':'')+'" tabindex="0" role="button" onclick="openLeaf(\\''+enc+'\\',\\''+t.sessionId+'\\')" onkeydown="if(event.key===\\'Enter\\'){openLeaf(\\''+enc+'\\',\\''+t.sessionId+'\\')}"><span class="ab stk '+k+'">'+(String(agentInitials(t.agent)).charAt(0)||'?')+'</span><span class="tt" data-user-content>'+_sbEsc(title(t.prompt))+'</span></div>';
46
+ });
47
+ var rest=matched.length-cap.length;
48
+ if(rest>0) h+='<div class="sb-more" role="button" tabindex="0" onclick="openRoom(\\''+enc+'\\')" onkeydown="if(event.key===\\'Enter\\'){openRoom(\\''+enc+'\\')}">+'+rest+(q?' more matches \\u00b7 open':' more \\u00b7 view all in project')+'</div>';
49
+ return h;
50
+ }
51
+ // Focus-safe search: re-render ONLY the leaves container (keep the input + caret).
52
+ function sbSearch(slug, enc, value){
53
+ _sbQuery[slug]=value;
54
+ var proj=document.querySelector('.sb-proj[data-slug="'+slug+'"]'); if(!proj) return;
55
+ var leavesEl=proj.querySelector('.sb-leaves'); if(!leavesEl) return;
56
+ var active=proj.classList.contains('active');
57
+ var tasks=active?((_roomThread&&_roomThread.tasks)||[]):_sbThreads[slug];
58
+ var caret=null, hadFocus=false; var oldI=leavesEl.querySelector('.sb-q');
59
+ if(oldI){ caret=oldI.selectionStart; hadFocus=(document.activeElement===oldI); }
60
+ leavesEl.innerHTML=sbLeavesHtml(tasks, decodeURIComponent(enc), slug, value);
61
+ if(hadFocus){ var ni=leavesEl.querySelector('.sb-q'); if(ni){ ni.focus(); if(caret!=null){try{ni.setSelectionRange(caret,caret);}catch(e){}} } }
62
+ }
63
+ // DESIGN.md §4.1 "your team" — the roster that makes the memorable thing ("my
64
+ // agents feel like a team") literal. Each agent is a row with an identity-hue
65
+ // presence dot; the one currently working pulses, an unavailable one dims to
66
+ // 35% and says the honest state. Fed by real readiness (/api/readiness/agents),
67
+ // working derived live from running tasks. Fetched once, re-rendered on poll.
68
+ var _teamAgents = null; // null = not fetched yet
69
+ var _teamFetching = false;
70
+ // The dot carries STATE, never identity. Identity is the agent's NAME, which is
71
+ // rendered right beside it. This used to be a TEAM_HUE map giving every agent
72
+ // its own colour — the per-agent rainbow that styles.ts declares dead and
73
+ // DESIGN.md §5 bans — and it survived in JS where a CSS gate could not see it.
74
+ var TEAM_STATE_COLOR = { working: 'var(--fg)', ready: 'var(--fg-70)', off: 'var(--fg-30)' };
75
+ function loadTeam(){
76
+ if(_teamFetching) return; _teamFetching=true;
77
+ fetch('/api/readiness/agents').then(function(r){return r.json();}).then(function(d){
78
+ _teamAgents=(d&&d.agents)||[]; _teamFetching=false; renderSidebar();
79
+ }).catch(function(){ _teamAgents=[]; _teamFetching=false; });
80
+ }
81
+ function teamSectionHtml(){
82
+ if(_teamAgents===null){ loadTeam(); return ''; }
83
+ if(!_teamAgents.length) return '';
84
+ // an agent is "working" if a running/queued task uses it right now
85
+ var live=(allS(_cached.tasks||{})||[]).filter(function(t){return ['running','queued'].indexOf(t.status)>=0;});
86
+ var working={}; live.forEach(function(t){ var a=String(t.agent||'').toLowerCase().replace(/\\s+/g,'-'); working[a]=1; });
87
+ var rows=_teamAgents.map(function(a){
88
+ var id=String(a.id||'').toLowerCase();
89
+ var ready=a.state==='ready';
90
+ var isWorking=!!working[id];
91
+ // Say what the state actually IS. This read \`a.state==='not_installed' ? 'not installed'
92
+ // : 'needs auth'\` — but the adapters emit 'needs_install', never 'not_installed' (that
93
+ // literal belongs to readiness.ts, a different subsystem with its own vocabulary). The
94
+ // comparison could not match, so EVERY non-ready agent fell through to "needs auth". On a
95
+ // machine with nothing installed the rail told the user all six agents needed
96
+ // authentication, sending them to look for a login screen for software they do not have.
97
+ // 'billed_key_required' landed there too, which is a third different answer.
98
+ var state=isWorking?'working':(ready?'ready'
99
+ :(a.state==='needs_install'||a.state==='not_installed')?'not installed'
100
+ :a.state==='billed_key_required'?'needs paid key'
101
+ :a.state==='needs_auth'?'needs auth'
102
+ :'unknown');
103
+ var hue=TEAM_STATE_COLOR[isWorking?'working':(ready?'ready':'off')];
104
+ var dot='<span class="sb-dot'+(isWorking?' working':'')+'" style="background:'+hue+'"></span>';
105
+ return '<div class="sb-mate" title="'+convHe(String(a.description||''))+'"><span class="sb-dot-wrap">'+dot+'</span><span class="sb-mate-nm" data-user-content>'+convHe(String(a.label||id))+'</span><span class="sb-mate-st">'+state+'</span></div>';
106
+ }).join('');
107
+ return '<div class="sb-head sb-team-head">Your team</div><div class="sb-team">'+rows+'</div>';
108
+ }
109
+ // Nav option B (Ega-approved 2026-07-06): the rail is THE navigator. Two
110
+ // destination rows above the roster, you-are-here = --bg-2 fill (DESIGN §4.1),
111
+ // derived from the same state the URL spine projects. The top-right
112
+ // Spend & Audit button and the room's "← Projects" back-link died for this —
113
+ // one way to move around, not three.
114
+ function navSectionHtml(){
115
+ var onSpend=_spendVisible();
116
+ var onHome=!_room && !_detail && !onSpend;
117
+ function row(here, click, label){
118
+ return '<div class="sb-nav-row'+(here?' here':'')+'" role="button" tabindex="0"'+(here?' aria-current="page"':'')
119
+ +' onclick="'+click+'" onkeydown="if(event.key===\\'Enter\\')'+click+'">'+label+'</div>';
120
+ }
121
+ return '<nav class="sb-nav" aria-label="Navigate">'
122
+ +row(onHome,'goHome()','Today')
123
+ +row(onSpend,'showSpend()','Spend &amp; Audit')
124
+ +'</nav>';
125
+ }
126
+ function goHome(){
127
+ // Widened gate (ship-review catch): the not-found screen nulls _detail but
128
+ // keeps the detail container visible — gating on _detail alone left "No task
129
+ // with this id here." stuck under a home URL. Same gate as the Esc handler.
130
+ if(_detail||document.body.classList.contains('detail-open')) hideD();
131
+ if(_spendVisible()){ document.getElementById('spend-view').style.display='none'; document.getElementById('task-list-view').style.display=''; }
132
+ if(_room) closeRoom(); else render();
133
+ // Home owns a CLEAN URL — hideD/closeRoom clear their own prefixes, but
134
+ // leaving Spend left '#/spend' under a home screen: a reload dragged the
135
+ // user back to Spend (loop-7 verifier catch, reproduced with a reload).
136
+ try{ if((location.hash||'').indexOf('#/')===0) history.replaceState(null,'',location.pathname+location.search); }catch(e){}
137
+ renderSidebar();
138
+ }
139
+ function renderSidebar(){
140
+ var el=document.getElementById('sidebar'); if(!el) return;
141
+ var ps=(_cached.projects||[]);
142
+ var html=navSectionHtml()+teamSectionHtml()+'<div class="sb-head">Projects<button class="sb-add" title="Clone a GitHub repo and open it as a project" aria-label="Add a project from GitHub" onclick="openClone()">+</button></div>';
143
+ if(!ps.length){
144
+ html+='<div class="sb-empty">no projects yet \\u00b7 describe a task below to start</div>';
145
+ } else {
146
+ ps.forEach(function(p){
147
+ var cwd=(p&&p.localPaths&&p.localPaths[0])||(p&&p.cwd)||'';
148
+ var slug=(p&&p.slug)||'';
149
+ var active=!!(_room && projPaths(p).indexOf(normC(_room))>=0);
150
+ // Explicit toggle wins; an active project DEFAULTS to expanded but can still
151
+ // be collapsed (the bug: 'active || toggled' forced the open one stuck open).
152
+ var expanded=(slug in _sbExpanded)?_sbExpanded[slug]:active;
153
+ var enc=encodeURIComponent(cwd);
154
+ var tasks = active
155
+ ? ((_roomThread && _roomThread.cwd===_room && _roomThread.tasks)?_roomThread.tasks:[])
156
+ : (expanded ? _sbThreads[slug] : []); // undefined => "loading"
157
+ html+='<div class="sb-proj'+(expanded?' expanded':'')+(active?' active':'')+'" data-slug="'+_sbEsc(slug)+'">'
158
+ +'<div class="sb-proj-head'+(active?' active':'')+'" role="button" tabindex="0" aria-expanded="'+(expanded?'true':'false')+'" onclick="clickSbProject(\\''+enc+'\\',\\''+_sbEsc(slug)+'\\',event)" onkeydown="if(event.key===\\'Enter\\'){clickSbProject(\\''+enc+'\\',\\''+_sbEsc(slug)+'\\',event)}">'
159
+ +'<span class="sb-car" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 6l6 6-6 6"/></svg></span>'
160
+ +'<span class="sb-pname" data-user-content>'+_sbEsc(projName(p))+'</span>'
161
+ +'<span class="ct">'+(p.taskCount||0)+'</span>'
162
+ +'</div>'
163
+ +'<div class="sb-leaves">'+(expanded?sbLeavesHtml(tasks, cwd, slug, _sbQuery[slug]):'')+'</div>'
164
+ +'</div>';
165
+ });
166
+ }
167
+ morphInto(el, html);
168
+ }
169
+ // Caret: toggle a project's tasks open/closed (CSS animates). First open lazily
170
+ // fetches that project's tasks (the active one already has _roomThread).
171
+ function toggleSbProject(slug, e){
172
+ if(e){ e.stopPropagation(); }
173
+ // Read the CURRENT rendered state (covers the active-defaults-expanded case) and
174
+ // flip it explicitly, so even the open project collapses.
175
+ var node=document.querySelector('.sb-proj[data-slug="'+slug+'"]');
176
+ var cur=node?node.classList.contains('expanded'):false;
177
+ _sbExpanded[slug]=!cur;
178
+ renderSidebar();
179
+ if(_sbExpanded[slug] && _sbThreads[slug]===undefined){
180
+ fetch('/api/projects/'+encodeURIComponent(slug)).then(function(x){return x.json();}).then(function(j){
181
+ _sbThreads[slug]=(j&&j.thread&&j.thread.tasks)||[]; renderSidebar();
182
+ }).catch(function(){ _sbThreads[slug]=[]; renderSidebar(); });
183
+ }
184
+ }
185
+ // Name: open the project in the main pane (and expand it).
186
+ function openSbProject(cwd, slug){ if(slug) _sbExpanded[slug]=true; openRoom(cwd); }
187
+ // Click anywhere on the project row → toggle its tree open/closed AND open it in
188
+ // the main pane. Users reach for the whole name, not just the caret; one click
189
+ // does both (the caret is now only the indicator).
190
+ function clickSbProject(enc, slug, e){ toggleSbProject(slug, e); openRoom(enc); }
191
+ // Leaf: open the project, then its run in the main pane (findS sees _sbThreads).
192
+ function openLeaf(cwd, sid){ openRoom(cwd); showD(sid); }
193
+ `;
194
+ //# sourceMappingURL=app-script-sidebar.js.map
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The replayable tour and its first-run helpers — the agent lineup detected on this machine,
3
+ * the example-prompt fill, and the tooltip walkthrough with its positioning.
4
+ *
5
+ * Split out of app-script.ts (strangler fig, split 13). Verbatim slice, interpolated back where
6
+ * it was cut so it keeps sharing the surrounding IIFE scope: the welcome card calls startTour,
7
+ * the palette offers "Go -> Take the tour", and the tooltip markup calls tourStep/endTour
8
+ * through inline handlers. Verified by hashing the composed APP_SCRIPT before and after: same
9
+ * bytes.
10
+ *
11
+ * The tour is deliberately never forced — it replaced a splash that was, and it stays
12
+ * reachable from the palette instead of ambushing a first run.
13
+ */
14
+ export const TOUR = `// First-run orientation: fill the composer from a clicked example prompt + focus
15
+ // it, so a new user goes one-click from "what do I type?" to an editable first task.
16
+ function exFill(s){ var ta=document.getElementById('main-ta'); if(ta){ ta.value=s; ta.focus(); try{ ta.dispatchEvent(new Event('input',{bubbles:true})); ta.setSelectionRange(s.length,s.length); }catch(e){} } }
17
+
18
+ // First-run onboarding: the coding agents detected on THIS machine, with REAL
19
+ // readiness (agentS over _cached.readiness). codehere's whole point is the
20
+ // cross-vendor lineup, so onboarding leads with it + nudges toward 2+ vendors.
21
+ function obAgentsHtml(){
22
+ var ags=(_cached.readiness&&_cached.readiness.agents)||[];
23
+ var byId={}; ags.forEach(function(a){ if(a&&(a.id||a.agent)) byId[a.id||a.agent]=a; });
24
+ var KNOWN=['claude-code','codex','aider','gemini','opencode']; var readyN=0; var chips='';
25
+ KNOWN.forEach(function(id){
26
+ var a=byId[id]; var st=a?agentS(a.state):null;
27
+ if(st==='ready') readyN++;
28
+ var word=st==='ready'?'ready':(st==='setup'?'setup':(st==='busy'?'busy':'not detected'));
29
+ var cls=st==='ready'?'ready':(st==='setup'?'setup':(st==='busy'?'busy':'off'));
30
+ chips+='<div class="ob-agent"><span class="ab stk '+agentKey(id)+'">'+(String(agentInitials(id)).charAt(0)||'?')+'</span><span class="ob-an">'+agentLabel(id)+'</span><span class="ob-ast"><span class="ob-dot '+cls+'"></span>'+word+'</span></div>';
31
+ });
32
+ var nudge = readyN>=2
33
+ ? '<div class="ob-hint">'+readyN+' agents ready. Run a task with one, then have a different vendor check it. That\\u2019s the cross-vendor proof.</div>'
34
+ : '<div class="ob-hint">'+readyN+' ready \\u00b7 install another CLI (codex, aider, gemini\\u2026) so codehere can prove one agent\\u2019s work with a different vendor.</div>';
35
+ return '<div class="ob-agents">'+chips+'</div>'+nudge;
36
+ }
37
+
38
+ // Interactive onboarding walkthrough — subtle spotlight coachmarks over the REAL
39
+ // UI (sidebar -> composer -> agent picker -> send), so a new user learns the
40
+ // actual controls by seeing them. Non-blocking (clicks pass through; you can act
41
+ // as you go), Next/Skip, shown once (localStorage), replayable from the welcome.
42
+ var TOUR=[
43
+ {sel:'#sidebar', title:'Your projects', body:'Every repo you work in lives here. Open one to see its runs.', pos:'right'},
44
+ {sel:'.sb-add', title:'1. Add a project', body:'New here? Point codehere at a GitHub repo (or a local folder) with +. This is the one way a project gets in.', pos:'right'},
45
+ {sel:'#main-ta', title:'2. Describe a task', body:'The other way in: with a project open, type what you want done. That puts an agent to work. No + needed.', pos:'top'},
46
+ {sel:'#agentpick-btn', title:'Pick who runs it', body:'Claude Code, Codex, Aider, Gemini, OpenCode. Or let Auto pick.', pos:'top'},
47
+ {sel:'#main-btn', title:'Send, and it\\u2019s on the record', body:'An agent picks it up. codehere records what it did, checks it against the real result, and shows the cost.', pos:'top'}
48
+ ];
49
+ var _tourChecked=false;
50
+ function startTour(){ var t=document.getElementById('ob-tour'); if(!t) return; t.hidden=false; tourStep(0); }
51
+ function endTour(markDone){ var t=document.getElementById('ob-tour'); if(t) t.hidden=true; if(markDone){ try{ localStorage.setItem('ch_tour_done','1'); }catch(e){} } }
52
+ function tourStep(i){
53
+ var step=TOUR[i]; if(!step){ endTour(true); return; }
54
+ var target=document.querySelector(step.sel);
55
+ if(!target){ tourStep(i+1); return; } // skip a target that isn't on this view
56
+ if(target.scrollIntoView){ try{ target.scrollIntoView({block:'nearest'}); }catch(e){} }
57
+ var r=target.getBoundingClientRect(); var pad=6;
58
+ var spot=document.getElementById('ob-spot');
59
+ spot.style.left=(r.left-pad)+'px'; spot.style.top=(r.top-pad)+'px';
60
+ spot.style.width=(r.width+pad*2)+'px'; spot.style.height=(r.height+pad*2)+'px';
61
+ var pop=document.getElementById('ob-pop');
62
+ var dots=''; for(var k=0;k<TOUR.length;k++) dots+='<span class="ob-pdot'+(k===i?' on':'')+'"></span>';
63
+ var last=(i+1>=TOUR.length);
64
+ pop.innerHTML='<div class="ob-pt">'+step.title+'</div><div class="ob-pb">'+step.body+'</div>'
65
+ +'<div class="ob-pf"><span class="ob-pdots">'+dots+'</span><span class="ob-pbtns">'
66
+ +'<button class="ob-skip" onclick="endTour(true)">'+(last?'':'Skip')+'</button>'
67
+ +'<button class="ob-next" onclick="'+(last?'endTour(true)':'tourStep('+(i+1)+')')+'">'+(last?'Got it':'Next')+'</button>'
68
+ +'</span></div>';
69
+ pop.style.visibility='hidden'; pop.style.left='0px'; pop.style.top='0px';
70
+ var pr=pop.getBoundingClientRect(); var left, top;
71
+ if(step.pos==='right'){ left=r.right+14; top=Math.max(12,r.top); }
72
+ else if(step.pos==='top'){ left=r.left+r.width/2-pr.width/2; top=r.top-pr.height-14; }
73
+ else { left=r.left; top=r.bottom+14; }
74
+ left=Math.max(12, Math.min(left, window.innerWidth-pr.width-12));
75
+ top=Math.max(12, Math.min(top, window.innerHeight-pr.height-12));
76
+ pop.style.left=left+'px'; pop.style.top=top+'px'; pop.style.visibility='visible';
77
+ }`;
78
+ //# sourceMappingURL=app-script-tour.js.map