@resolveio/client-lib-aicoder-dashboard 21.0.11 → 21.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import*as i0 from"@angular/core";import{Injectable,EventEmitter,Output,Input,Component,NgModule}from"@angular/core";import*as i1 from"@angular/common/http";import{HttpHeaders,HttpClientModule}from"@angular/common/http";import*as i2 from"@resolveio/client-lib-core";import{Observable,throwError,interval}from"rxjs";import{switchMap,map,tap,shareReplay}from"rxjs/operators";import*as i2$1 from"@angular/forms";import{FormsModule}from"@angular/forms";import{CommonModule}from"@angular/common";class AICoderDashboardService{buildDefaultSummary(){return{overview:{app_name:"ResolveIO App",app_status:"Active",plan_tier:"small",max_users:10},database:{name:"resolveio",host:"127.0.0.1",status:"Ready"},infrastructure:{current_tier:"small",available_tiers:["small","medium","large"],max_users:10,backend_status:"Ready"},code_changes:{release_notes:"Latest updates are live and available.",last_deployed_at:"",recent_updates:["Overview tab highlights app status and plan tier.","Infrastructure tab shows Small, Medium, and Large options.","Code Changes tab summarizes customer-facing updates."]},subscription:{current_package:"Small",current_tier:"small",package_price_label:"$99 / month",billing_status:"Paid",billing_mode:"Subscription",subscription_status:"active",current_period_end:"",manage_url:"",can_downgrade:!0,downgrade_reason:""}}}buildTierOptions(){return[{id:"small",label:"Small",description:"Great for focused teams and lighter usage."},{id:"medium",label:"Medium",description:"Balanced capacity for growing teams and workflows."},{id:"large",label:"Large",description:"Higher capacity for heavy usage and larger teams."}]}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardService,deps:[],target:i0.ɵɵFactoryTarget.Injectable});static"ɵprov"=i0.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardService,providedIn:"root"})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardService,decorators:[{type:Injectable,args:[{providedIn:"root"}]}]});class AICoderDashboardComponent{dashboardService;summary;loading=!1;errorMessage="";activeTab="overview";tierOptions;tabChange=new EventEmitter;retry=new EventEmitter;tierChange=new EventEmitter;manageSubscription=new EventEmitter;constructor(t){this.dashboardService=t,this.summary=this.dashboardService.buildDefaultSummary(),this.tierOptions=this.dashboardService.buildTierOptions()}setTab(t){this.activeTab!==t&&this.tabChange.emit(t)}requestRetry(){this.retry.emit()}requestTierChange(t){this.tierChange.emit(t)}requestManageSubscription(){this.manageSubscription.emit(this.resolveManageSubscriptionUrl())}isCurrentTier(t){return String(this.summary?.infrastructure?.current_tier||"").trim().toLowerCase()===t}hasManageSubscriptionUrl(){return!!this.resolveManageSubscriptionUrl()}resolveManageSubscriptionUrl(){return String(this.summary?.subscription?.manage_url||"").trim()}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardComponent,deps:[{token:AICoderDashboardService}],target:i0.ɵɵFactoryTarget.Component});static"ɵcmp"=i0.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"21.1.2",type:AICoderDashboardComponent,isStandalone:!1,selector:"resolveio-client-lib-aicoder-dashboard",inputs:{summary:"summary",loading:"loading",errorMessage:"errorMessage",activeTab:"activeTab",tierOptions:"tierOptions"},outputs:{tabChange:"tabChange",retry:"retry",tierChange:"tierChange",manageSubscription:"manageSubscription"},ngImport:i0,template:'<div class="aicoder-dashboard">\n\t<div class="aicoder-dashboard__tabs">\n\t\t<button type="button" [class.is-active]="activeTab === \'overview\'" (click)="setTab(\'overview\')">Overview</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'database\'" (click)="setTab(\'database\')">Database</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'infrastructure\'" (click)="setTab(\'infrastructure\')">Infrastructure</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'code_changes\'" (click)="setTab(\'code_changes\')">Code Changes</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'subscription\'" (click)="setTab(\'subscription\')">Subscription</button>\n\t</div>\n\n\t@if (loading) {\n\t\t<div class="aicoder-dashboard__state">Loading dashboard details...</div>\n\t} @else if (errorMessage) {\n\t\t<div class="aicoder-dashboard__state aicoder-dashboard__state--error">\n\t\t\t<span>{{ errorMessage }}</span>\n\t\t\t<button type="button" (click)="requestRetry()">Retry</button>\n\t\t</div>\n\t} @else {\n\t\t@if (activeTab === \'overview\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>App</span><strong>{{ summary.overview.app_name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.overview.app_status }}</strong></div>\n\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.overview.plan_tier }}</strong></div>\n\t\t\t\t<div class="tile"><span>Max users</span><strong>{{ summary.overview.max_users }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'database\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>Name</span><strong>{{ summary.database.name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Host</span><strong>{{ summary.database.host }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.database.status }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'infrastructure\') {\n\t\t\t<div class="aicoder-dashboard__tiers">\n\t\t\t\t@for (tier of tierOptions; track tier.id) {\n\t\t\t\t\t<div class="tier" [class.tier--active]="isCurrentTier(tier.id)">\n\t\t\t\t\t\t<h4>{{ tier.label }}</h4>\n\t\t\t\t\t\t<p>{{ tier.description }}</p>\n\t\t\t\t\t\t<button type="button" (click)="requestTierChange(tier.id)">Choose {{ tier.label }}</button>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'code_changes\') {\n\t\t\t<div class="aicoder-dashboard__changes">\n\t\t\t\t<p>{{ summary.code_changes.release_notes }}</p>\n\t\t\t\t<ul>\n\t\t\t\t\t@for (item of summary.code_changes.recent_updates; track item) {\n\t\t\t\t\t\t<li>{{ item }}</li>\n\t\t\t\t\t}\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'subscription\') {\n\t\t\t<div class="aicoder-dashboard__subscription">\n\t\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t\t<div class="tile"><span>Package</span><strong>{{ summary.subscription?.current_package || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.subscription?.current_tier || summary.overview.plan_tier || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Price</span><strong>{{ summary.subscription?.package_price_label || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing status</span><strong>{{ summary.subscription?.billing_status || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing mode</span><strong>{{ summary.subscription?.billing_mode || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Subscription</span><strong>{{ summary.subscription?.subscription_status || \'N/A\' }}</strong></div>\n\t\t\t\t</div>\n\t\t\t\t@if (summary.subscription?.current_period_end) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint">Current period ends: {{ summary.subscription?.current_period_end }}</p>\n\t\t\t\t}\n\t\t\t\t@if (summary.subscription?.downgrade_reason) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint aicoder-dashboard__hint--warning">{{ summary.subscription?.downgrade_reason }}</p>\n\t\t\t\t}\n\t\t\t\t<div class="aicoder-dashboard__subscription-actions">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype="button"\n\t\t\t\t\t\tclass="aicoder-dashboard__primary-action"\n\t\t\t\t\t\t(click)="requestManageSubscription()"\n\t\t\t\t\t\t[disabled]="!hasManageSubscriptionUrl()"\n\t\t\t\t\t>\n\t\t\t\t\t\tUpgrade Or Manage Plan\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t}\n\t}\n</div>\n',styles:[".aicoder-dashboard{--aicoder-primary: #0f4c81;font-family:inherit}.aicoder-dashboard__tabs{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:.75rem}.aicoder-dashboard__tabs button{border:1px solid rgba(15,23,42,.2);background:#fff;padding:.45rem .75rem;border-radius:8px;font-weight:600}.aicoder-dashboard__tabs button.is-active{border-color:var(--aicoder-primary);background:#0f4c811a}.aicoder-dashboard__state{padding:.85rem;border-radius:10px;background:#f8fafc}.aicoder-dashboard__state--error{display:flex;justify-content:space-between;align-items:center;gap:.5rem;background:#fff7ed}.aicoder-dashboard__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem}.tile{padding:.8rem;border:1px solid rgba(15,23,42,.12);border-radius:10px;background:#fff}.tile span{display:block;font-size:.74rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#0f172a9e}.tile strong{display:block;margin-top:.3rem;font-size:1rem}.aicoder-dashboard__tiers{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:.75rem}.tier{padding:.85rem;border-radius:10px;border:1px solid rgba(15,23,42,.12);background:#f8fafc}.tier--active{border-color:var(--aicoder-primary);background:#0f4c8114}.tier h4{margin:0}.tier p{margin:.45rem 0}.aicoder-dashboard__changes ul{margin:0;padding-left:1.1rem}.aicoder-dashboard__subscription{display:grid;gap:.75rem}.aicoder-dashboard__subscription-actions{display:flex;flex-wrap:wrap;gap:.5rem}.aicoder-dashboard__primary-action{border:1px solid var(--aicoder-primary);background:var(--aicoder-primary);color:#fff;padding:.5rem .85rem;border-radius:8px;font-weight:700}.aicoder-dashboard__primary-action[disabled]{opacity:.55;cursor:not-allowed}.aicoder-dashboard__hint{margin:0;color:#334155;font-size:.88rem}.aicoder-dashboard__hint--warning{color:#9a3412}\n"]})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardComponent,decorators:[{type:Component,args:[{selector:"resolveio-client-lib-aicoder-dashboard",standalone:!1,template:'<div class="aicoder-dashboard">\n\t<div class="aicoder-dashboard__tabs">\n\t\t<button type="button" [class.is-active]="activeTab === \'overview\'" (click)="setTab(\'overview\')">Overview</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'database\'" (click)="setTab(\'database\')">Database</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'infrastructure\'" (click)="setTab(\'infrastructure\')">Infrastructure</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'code_changes\'" (click)="setTab(\'code_changes\')">Code Changes</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'subscription\'" (click)="setTab(\'subscription\')">Subscription</button>\n\t</div>\n\n\t@if (loading) {\n\t\t<div class="aicoder-dashboard__state">Loading dashboard details...</div>\n\t} @else if (errorMessage) {\n\t\t<div class="aicoder-dashboard__state aicoder-dashboard__state--error">\n\t\t\t<span>{{ errorMessage }}</span>\n\t\t\t<button type="button" (click)="requestRetry()">Retry</button>\n\t\t</div>\n\t} @else {\n\t\t@if (activeTab === \'overview\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>App</span><strong>{{ summary.overview.app_name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.overview.app_status }}</strong></div>\n\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.overview.plan_tier }}</strong></div>\n\t\t\t\t<div class="tile"><span>Max users</span><strong>{{ summary.overview.max_users }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'database\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>Name</span><strong>{{ summary.database.name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Host</span><strong>{{ summary.database.host }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.database.status }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'infrastructure\') {\n\t\t\t<div class="aicoder-dashboard__tiers">\n\t\t\t\t@for (tier of tierOptions; track tier.id) {\n\t\t\t\t\t<div class="tier" [class.tier--active]="isCurrentTier(tier.id)">\n\t\t\t\t\t\t<h4>{{ tier.label }}</h4>\n\t\t\t\t\t\t<p>{{ tier.description }}</p>\n\t\t\t\t\t\t<button type="button" (click)="requestTierChange(tier.id)">Choose {{ tier.label }}</button>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'code_changes\') {\n\t\t\t<div class="aicoder-dashboard__changes">\n\t\t\t\t<p>{{ summary.code_changes.release_notes }}</p>\n\t\t\t\t<ul>\n\t\t\t\t\t@for (item of summary.code_changes.recent_updates; track item) {\n\t\t\t\t\t\t<li>{{ item }}</li>\n\t\t\t\t\t}\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'subscription\') {\n\t\t\t<div class="aicoder-dashboard__subscription">\n\t\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t\t<div class="tile"><span>Package</span><strong>{{ summary.subscription?.current_package || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.subscription?.current_tier || summary.overview.plan_tier || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Price</span><strong>{{ summary.subscription?.package_price_label || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing status</span><strong>{{ summary.subscription?.billing_status || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing mode</span><strong>{{ summary.subscription?.billing_mode || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Subscription</span><strong>{{ summary.subscription?.subscription_status || \'N/A\' }}</strong></div>\n\t\t\t\t</div>\n\t\t\t\t@if (summary.subscription?.current_period_end) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint">Current period ends: {{ summary.subscription?.current_period_end }}</p>\n\t\t\t\t}\n\t\t\t\t@if (summary.subscription?.downgrade_reason) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint aicoder-dashboard__hint--warning">{{ summary.subscription?.downgrade_reason }}</p>\n\t\t\t\t}\n\t\t\t\t<div class="aicoder-dashboard__subscription-actions">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype="button"\n\t\t\t\t\t\tclass="aicoder-dashboard__primary-action"\n\t\t\t\t\t\t(click)="requestManageSubscription()"\n\t\t\t\t\t\t[disabled]="!hasManageSubscriptionUrl()"\n\t\t\t\t\t>\n\t\t\t\t\t\tUpgrade Or Manage Plan\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t}\n\t}\n</div>\n',styles:[".aicoder-dashboard{--aicoder-primary: #0f4c81;font-family:inherit}.aicoder-dashboard__tabs{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:.75rem}.aicoder-dashboard__tabs button{border:1px solid rgba(15,23,42,.2);background:#fff;padding:.45rem .75rem;border-radius:8px;font-weight:600}.aicoder-dashboard__tabs button.is-active{border-color:var(--aicoder-primary);background:#0f4c811a}.aicoder-dashboard__state{padding:.85rem;border-radius:10px;background:#f8fafc}.aicoder-dashboard__state--error{display:flex;justify-content:space-between;align-items:center;gap:.5rem;background:#fff7ed}.aicoder-dashboard__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem}.tile{padding:.8rem;border:1px solid rgba(15,23,42,.12);border-radius:10px;background:#fff}.tile span{display:block;font-size:.74rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#0f172a9e}.tile strong{display:block;margin-top:.3rem;font-size:1rem}.aicoder-dashboard__tiers{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:.75rem}.tier{padding:.85rem;border-radius:10px;border:1px solid rgba(15,23,42,.12);background:#f8fafc}.tier--active{border-color:var(--aicoder-primary);background:#0f4c8114}.tier h4{margin:0}.tier p{margin:.45rem 0}.aicoder-dashboard__changes ul{margin:0;padding-left:1.1rem}.aicoder-dashboard__subscription{display:grid;gap:.75rem}.aicoder-dashboard__subscription-actions{display:flex;flex-wrap:wrap;gap:.5rem}.aicoder-dashboard__primary-action{border:1px solid var(--aicoder-primary);background:var(--aicoder-primary);color:#fff;padding:.5rem .85rem;border-radius:8px;font-weight:700}.aicoder-dashboard__primary-action[disabled]{opacity:.55;cursor:not-allowed}.aicoder-dashboard__hint{margin:0;color:#334155;font-size:.88rem}.aicoder-dashboard__hint--warning{color:#9a3412}\n"]}]}],ctorParameters:()=>[{type:AICoderDashboardService}],propDecorators:{summary:[{type:Input}],loading:[{type:Input}],errorMessage:[{type:Input}],activeTab:[{type:Input}],tierOptions:[{type:Input}],tabChange:[{type:Output}],retry:[{type:Output}],tierChange:[{type:Output}],manageSubscription:[{type:Output}]}});class AICoderWorkbenchApiService{http;tokenManager;sessionHeaderName="X-AI-Coder-Session";appTokenHeaderName="X-AI-Coder-App-Token";sessionRefreshSkewMs=6e4;defaultSessionTtlMs=144e5;sessionByApiBase=new Map;exchangeByApiBase=new Map;constructor(t,e){this.http=t,this.tokenManager=e}get(t,e,r=""){return this.authHeaders(t,r).pipe(switchMap(r=>this.http.get(this.buildUrl(t,e),{headers:r})))}post(t,e,r={},i=""){return this.authHeaders(t,i).pipe(switchMap(i=>this.http.post(this.buildUrl(t,e),r,{headers:i})))}patch(t,e,r={},i=""){return this.authHeaders(t,i).pipe(switchMap(i=>this.http.patch(this.buildUrl(t,e),r,{headers:i})))}delete(t,e,r=""){return this.authHeaders(t,r).pipe(switchMap(r=>this.http.delete(this.buildUrl(t,e),{headers:r})))}resolveDefaultApiBase(){const t=String(window?.location?.hostname||"").trim().toLowerCase();return"aicoder.resolveio.com"===t||t.endsWith(".aicoder.resolveio.com")?"https://backend.aicoder.resolveio.com":""}authHeaders(t,e=""){const r=String(e||"").trim();return r?new Observable(t=>{t.next(new HttpHeaders({[this.appTokenHeaderName]:r})),t.complete()}):this.ensureSessionToken(t).pipe(map(t=>new HttpHeaders({[this.sessionHeaderName]:t})))}ensureSessionToken(t){const e=this.normalizeApiBase(t),r=this.sessionByApiBase.get(e);if(r&&r.token&&r.expiresAtMs>Date.now()+this.sessionRefreshSkewMs)return new Observable(t=>{t.next(r.token),t.complete()});const i=this.exchangeByApiBase.get(e);if(i)return i;const a=String(this.tokenManager.getToken("accessToken")||"").trim();if(!a)return throwError(()=>new Error("Sign in before using AICoder."));const s=new HttpHeaders({Authorization:`Bearer ${a}`}),o=this.http.post(this.buildUrl(e,"/api/ai-coder/auth/session"),{},{headers:s}).pipe(map(t=>{const r=String(t?.token||"").trim();if(!r)throw new Error("AICoder session response did not include a token.");return this.sessionByApiBase.set(e,{token:r,expiresAtMs:this.resolveSessionExpiresAtMs(t)}),r}),tap({error:()=>this.sessionByApiBase.delete(e),complete:()=>this.exchangeByApiBase.delete(e)}),shareReplay(1));return this.exchangeByApiBase.set(e,o),o}resolveSessionExpiresAtMs(t){const e=t?.expires_at?new Date(t.expires_at).getTime():0;if(Number.isFinite(e)&&e>Date.now())return e;const r=1e3*Number(t?.expires_in_seconds||0);return Date.now()+(r>0?r:this.defaultSessionTtlMs)}buildUrl(t,e){const r=this.normalizeApiBase(t),i=String(e||"").trim();return r?`${r}${i.startsWith("/")?i:`/${i}`}`:i}normalizeApiBase(t){return String(t||"").trim().replace(/\/+$/,"")}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,deps:[{token:i1.HttpClient},{token:i2.TokenManagerService}],target:i0.ɵɵFactoryTarget.Injectable});static"ɵprov"=i0.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,providedIn:"root"})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,decorators:[{type:Injectable,args:[{providedIn:"root"}]}],ctorParameters:()=>[{type:i1.HttpClient},{type:i2.TokenManagerService}]});class AICoderWorkbenchComponent{api;appId="";appName="AICoder App";apiBase="";appToken="";defaultTab="chats";compact=!1;showHeader=!0;autoRefresh=!0;activeTab="chats";jobs=[];selectedJob=null;jobLogs=[];qaEvidence=[];deployOperations=[];deployJobs=[];gitStatus=null;isLoading=!1;isLoadingQaEvidence=!1;isSubmitting=!1;errorMessage="";successMessage="";requestTitle="";requestMessage="";chatMessage="";workflowMode="implementation";requestKind="bug_or_feature";dataChangePolicy="review_before_write";deploymentTarget="branch_pr";evidenceNotes="";qaFocus="";riskNotes="";refreshSub;constructor(t){this.api=t}ngOnInit(){this.activeTab=this.normalizeTab(this.defaultTab),this.apiBase||(this.apiBase=this.api.resolveDefaultApiBase()),this.refresh(),this.startAutoRefresh()}ngOnChanges(t){t.defaultTab&&this.defaultTab&&(this.activeTab=this.normalizeTab(this.defaultTab)),(t.appId&&!t.appId.firstChange||t.apiBase&&!t.apiBase.firstChange)&&this.refresh()}ngOnDestroy(){this.refreshSub?.unsubscribe()}setTab(t){this.activeTab=this.normalizeTab(t),"git"!==t||this.gitStatus||this.loadGitStatus(),"deploy"!==t||this.deployOperations.length||this.loadDeployHistory()}refresh(){this.normalizedAppId?(this.loadJobs(),this.loadDeployHistory(),this.loadGitStatus()):this.errorMessage="AICoder app id is missing."}selectJob(t){this.selectedJob=t,this.loadJobLogs(t._id),this.loadJobQaEvidence(t._id)}submitRequest(){if(!this.normalizedAppId||this.isSubmitting)return;const t=String(this.requestMessage||"").trim();if(!t)return void(this.errorMessage="Describe what you want changed before starting the chat.");this.isSubmitting=!0,this.clearMessages();const e=this.isReadOnlyQuestion(t),r=e?`/api/ai-coder/apps/${this.normalizedAppId}/questions`:`/api/ai-coder/apps/${this.normalizedAppId}/requests`;this.api.post(this.apiBase,r,{title:String(this.requestTitle||"").trim(),message:t,workflow_mode:this.workflowMode,request_kind:e?"question":this.requestKind,data_change_policy:this.dataChangePolicy,deployment_target:this.deploymentTarget,evidence_notes:this.evidenceNotes,qa_focus:this.qaFocus,risk_notes:this.riskNotes},this.normalizedAppToken).subscribe({next:t=>{if(this.successMessage=e?"AI answered your question.":"AI chat started.",this.requestTitle="",this.requestMessage="",t?.job){const e=this.normalizeJob(t.job);this.jobs=[e,...this.jobs.filter(t=>t._id!==e._id)],this.selectJob(e)}},error:t=>this.handleError(t,"Could not start the AI chat."),complete:()=>this.isSubmitting=!1})}sendChatMessage(){const t=this.selectedJobId,e=String(this.chatMessage||"").trim();if(!t||!e||this.isSubmitting)return;this.isSubmitting=!0,this.clearMessages();const r=this.selectedJobIsQuestionOnly?`/api/ai-coder/jobs/${t}/question`:`/api/ai-coder/jobs/${t}/prompt`;this.api.post(this.apiBase,r,{message:e},this.normalizedAppToken).subscribe({next:t=>{this.successMessage=this.selectedJobIsQuestionOnly?"AI answered your question.":"Message sent to AICoder.",this.chatMessage="",t?.job&&(this.mergeJob(t.job),this.selectJob(t.job))},error:t=>this.handleError(t,"Could not send the message."),complete:()=>this.isSubmitting=!1})}approveSelectedJob(){this.runJobAction("approve","Approved.")}createPullRequest(){this.runJobAction("create-pr","Test review started.")}publishSelectedJob(){this.runJobAction("publish","Approved for live deploy.")}sendToTestSite(){this.canSendSelectedJobToTestSite&&this.runJobAction("test-deploy","Test site is ready.")}approveAndDeployLive(){this.canApproveSelectedJobLive&&window.confirm("Put this tested version live now? The temporary test site will be removed after this starts.")&&this.runJobAction("publish","This version is being put live.")}deleteSelectedChat(){const t=this.selectedJobId;t&&!this.isSubmitting&&window.confirm("Delete this AI chat? Any temporary test site for this chat will be removed too.")&&(this.isSubmitting=!0,this.clearMessages(),this.api.delete(this.apiBase,`/api/ai-coder/jobs/${t}`,this.normalizedAppToken).subscribe({next:()=>{this.successMessage="AI chat deleted.",this.jobs=this.jobs.filter(e=>e._id!==t),this.selectedJob=this.jobs[0]||null,this.jobLogs=[],this.qaEvidence=[],this.selectedJob&&(this.loadJobLogs(this.selectedJob._id),this.loadJobQaEvidence(this.selectedJob._id))},error:t=>this.handleError(t,"Could not delete the AI chat."),complete:()=>this.isSubmitting=!1}))}queueDeploy(t){this.normalizedAppId&&!this.isSubmitting&&(this.isSubmitting=!0,this.clearMessages(),this.api.post(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/deploy`,{deploy_client:"full"===t||"client"===t,deploy_backend:"full"===t||"backend"===t,clear_destination:!0,invalidate:!0},this.normalizedAppToken).subscribe({next:()=>{this.successMessage="full"===t?"Full deploy queued.":("client"===t?"Client":"Server")+" deploy queued.",this.loadDeployHistory()},error:t=>this.handleError(t,"Could not queue deploy."),complete:()=>this.isSubmitting=!1}))}openPr(t=this.selectedJob){const e=this.resolvePrUrl(t);e&&window.open(e,"_blank","noopener")}openTestSite(){const t=this.selectedJobTestSiteUrl;t&&window.open(t,"_blank","noopener")}loadJobs(){this.normalizedAppId&&(this.isLoading=!0,this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/jobs`,this.normalizedAppToken).subscribe({next:t=>{const e=this.selectedJobId;this.jobs=(t?.jobs||[]).map(t=>this.normalizeJob(t));const r=e&&this.jobs.find(t=>t._id===e)||null;r?(this.selectedJob=r,this.loadJobLogs(r._id),this.loadJobQaEvidence(r._id)):!this.selectedJob&&this.visibleJobs.length&&this.selectJob(this.visibleJobs[0])},error:t=>this.handleError(t,"Could not load AI chats."),complete:()=>this.isLoading=!1}))}loadJobLogs(t){const e=String(t||"").trim();e?this.api.get(this.apiBase,`/api/ai-coder/jobs/${e}/logs?limit=80`,this.normalizedAppToken).subscribe({next:t=>this.jobLogs=this.buildTimeline(this.selectedJob,t?.logs||[]),error:t=>this.handleError(t,"Could not load chat history.")}):this.jobLogs=[]}loadJobQaEvidence(t){const e=String(t||"").trim();e?(this.isLoadingQaEvidence=!0,this.api.get(this.apiBase,`/api/ai-coder/jobs/${e}/qa-evidence`,this.normalizedAppToken).subscribe({next:t=>this.qaEvidence=(t?.evidence||[]).filter(t=>this.resolveQaEvidenceUrl(t)),error:()=>{this.qaEvidence=[],this.isLoadingQaEvidence=!1},complete:()=>this.isLoadingQaEvidence=!1})):this.qaEvidence=[]}loadDeployHistory(){this.normalizedAppId&&this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/deploy/history`,this.normalizedAppToken).subscribe({next:t=>{this.deployOperations=t?.operations||[],this.deployJobs=t?.jobs||t?.deploy_jobs||[]},error:t=>this.handleError(t,"Could not load deploy history.")})}loadGitStatus(){this.normalizedAppId&&this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/git`,this.normalizedAppToken).subscribe({next:t=>this.gitStatus=t?.status||t||null,error:t=>this.handleError(t,"Could not load git status.")})}get normalizedAppId(){return String(this.appId||"").trim()}get selectedJobId(){return String(this.selectedJob?._id||"").trim()}get normalizedAppToken(){return String(this.appToken||"").trim()}get activeDeploys(){return this.deployOperations.filter(t=>"In Progress"===String(t?.status||"").trim())}get recentDeploys(){return this.deployOperations.filter(t=>"In Progress"!==String(t?.status||"").trim()).slice(0,6)}get visibleJobs(){return this.computeVisibleJobs(this.jobs)}get hasSelectedJob(){return!!this.selectedJob}get selectedJobIsWorking(){return this.isActiveJob(this.selectedJob)}get selectedJobIsQuestionOnly(){return this.isQuestionOnlyJob(this.selectedJob)}get selectedJobHasChanges(){return this.hasJobChanges(this.selectedJob)}get selectedJobHasReview(){return!!this.resolvePrUrl(this.selectedJob)}get selectedJobTestSiteUrl(){return this.resolveTestSiteUrl(this.selectedJob)}get canSendSelectedJobToTestSite(){return!!this.selectedJobId&&!this.isSubmitting&&!this.selectedJobIsWorking&&this.selectedJobHasChanges&&!this.selectedJobTestSiteUrl}get canApproveSelectedJobLive(){return!!this.selectedJobId&&!this.isSubmitting&&!this.selectedJobIsWorking&&this.selectedJobHasChanges&&(!!this.selectedJobTestSiteUrl||this.selectedJobHasReview)}get selectedJobNextStepMessage(){return this.selectedJob?this.selectedJobIsQuestionOnly?"":this.selectedJobIsWorking?"I am working on this. I will update this chat when there is something ready for you to review.":this.selectedJobTestSiteUrl?"Your test site is ready. Open it and check the change before putting it live.":this.selectedJobHasReview?"A test version is ready. Open it first, then approve it when everything looks right.":this.selectedJobHasChanges?"The update is ready. Send it to the test site when you are ready.":this.isFailedJob(this.selectedJob)?"This needs your attention before it can continue. Add a reply with what you want to try next.":"This chat will update when there is something new.":""}isActiveJob(t){if(this.isQuestionOnlyJob(t)&&"COMPLETE"===String(t?.phase||"").toUpperCase())return!1;const e=String(t?.status||"").toLowerCase(),r=String(t?.phase||t?.stage||"").toLowerCase();return!(e.includes("fail")||e.includes("error")||e.includes("complete")||e.includes("passed"))&&(!r.includes("complete")&&!r.includes("review")&&("queued"===e||"in progress"===e||"running"===e||"pending"===e||["discovery","planning","execution","linting","building"].includes(r)))}resolvePrUrl(t){return String(t?.pr_url||t?.pull_request_url||"").trim()}resolveTestSiteUrl(t){return String(t?.testSiteUrl||t?.test_website_url||t?.test_url||"").trim()}friendlyJobStatus(t){if(this.isQuestionOnlyJob(t))return this.isActiveJob(t)?"AI is answering":"Answered";const e=String(t?.status||t?.stage||"").trim().toLowerCase(),r=String(t?.phase||"").trim().toLowerCase();return e||r?e.includes("complete")||e.includes("passed")||r.includes("complete")?this.resolveTestSiteUrl(t)?"Test site ready":this.resolvePrUrl(t)?"Ready to test":"Ready to review":e.includes("fail")||e.includes("error")?"Needs attention":e.includes("progress")||e.includes("running")||e.includes("pending")||e.includes("queued")?"AI is working":e.includes("pause")||e.includes("stopped")?"Paused":this.toTitle(e||r):"Waiting to start"}friendlyDeployStatus(t){const e=String(t?.status||"").trim(),r=String(t?.type||"").trim();if(!e&&!r)return"Website update";return`${r?this.toTitle(r.replace(/ai coder/gi,"").replace(/backend/gi,"server")):"Website update"}${e?` - ${e}`:""}`}formatDate(t){if(!t)return"";const e=t instanceof Date?t:new Date(t);return Number.isNaN(e.getTime())?String(t):e.toLocaleString()}resolveQaEvidenceUrl(t){return String(t?.url||t?.data_url||"").trim()}buildTimeline(t,e){const r=[],i=this.isQuestionOnlyJob(t),a=t=>{const e=this.sanitizeUserFacingMessage(t.message);if(!e)return;const i=`${t.role}:${t.label}:${t.date||""}:${e}`;r.some(t=>t.id===i||t.message===e)||r.push({...t,id:i,message:e})};(Array.isArray(t?.conversation)&&t?.conversation||[]).forEach((e,r)=>{const s="assistant"===String(e?.role||"").toLowerCase()?"assistant":"user",o="user"===s?this.extractUserRequest(e?.message||""):String(e?.message||"");a({role:s,label:"user"===s?"You":"Assistant",message:o,date:this.formatDate(e?.timestamp||e?.createdAt),active:!1}),0===r&&"user"===s&&this.isActiveJob(t)&&a({role:"assistant",label:"Assistant",message:i?"I am checking this and will answer without changing anything.":"I am working on this. I will update this chat when there is something ready for you to review.",date:"",active:!0})});const s=this.extractQuestionAnswerFromSummary(t);return s&&a({role:"assistant",label:"Assistant",message:s,date:this.formatDate(t?.updatedAt),active:!1}),!r.length&&this.isActiveJob(t)&&a({role:"assistant",label:"Assistant",message:i?"I am reading this and will answer shortly.":"I am working on this. I will update this chat when there is something ready for you to review.",date:"",active:!0}),r}extractUserRequest(t){const e=String(t||"").trim(),r=e.match(/(?:^|\n)Request:\s*([\s\S]*?)(?:\n\n|(?:^|\n)AICoder case runner contract:|$)/i);return r?.[1]?r[1].trim():e.split("\n").filter(t=>!t.match(/^(This is a change request|App:|Template:|App root:|AICoder|Case-building|Data safety|QA playbook|Branch, PR|Constraints:|- )/i)).join("\n").trim()}sanitizeUserFacingMessage(t){const e=String(t||"").replace(/```[\s\S]*?```/g,"[details hidden]").replace(/^\s*\[[^\]]+\]\s*/,"").replace(/\/var\/ai-workspace\/[^\s)]+/g,"the app workspace").replace(/ResolveIO\/aicoder-[\w-]+/g,"the app repository").replace(/apps\/[a-f0-9]{24}/gi,"the app").replace(/[a-f0-9]{24}/gi,"the app").replace(/\[([^\]]+)\]\([^)]+\)/g,(t,e)=>this.isInternalText(e)?"the app":e).replace(/\[([^\]]+)\]/g,(t,e)=>this.isInternalText(e)?"":e).trim();if(!e||this.isInternalOnlyMessage(e))return"";let r=!1;return e.split(/\r?\n/).map(t=>this.cleanUserFacingLine(t)).filter(t=>{const e=t.trim();return!!e&&(!this.isInternalLine(e)||!(!/^verification\s*:/i.test(e)||r)&&(r=!0,!0))}).map(t=>/^verification\s*:/i.test(t)?"I checked the update after making the change.":t).join("\n").replace(/\n{3,}/g,"\n\n").trim()}cleanUserFacingLine(t){return String(t||"").replace(/^\s*[-*]\s+/,"").replace(/^\[[^\]]+\]\s*/,"").replace(/^(targeted ui maintenance change|maintenance change|implementation update|change summary|summary)\s*/i,"").replace(/`([^`]+)`/g,(t,e)=>this.isInternalText(e)?"the app":e).replace(/\btop-nav\b/gi,"top navigation").replace(/\s+from the app\b[\s\S]*$/i,".").replace(/\s+/g," ").trim()}isInternalOnlyMessage(t){const e=String(t||"").toLowerCase();return e.includes("deterministic review")||e.includes("change request final review")||e.includes("cycle 1 summary")||e.includes("gates ")||e.includes("lint:pass")||e.includes("build:pass")||e.includes("review:pass")||e.includes("validate:pass")||e.includes("modified files captured")}isInternalLine(t){const e=String(t||"").toLowerCase();return/^verification\s*:/i.test(t)||this.isInternalText(t)||e.includes("grep")||e.includes("npm ")||e.includes("lint")||e.includes("build-dev")||e.includes("build-prod")||e.includes("workspace")||e.includes("routerlink")||e.includes("navtabs")||e.includes("fixtures")||e.includes("migrations")||e.includes("schemas")||e.includes("server flows")||e.includes("package files")||e.includes("deterministic review")||e.includes("cycle ")||e.includes("gates ")}isInternalText(t){const e=String(t||"").trim();return!!e&&(/(^|[\s"'(])(?:\.?\/)?[\w.-]+\/[\w./-]+/.test(e)||/\b[\w.-]+\.(ts|tsx|js|jsx|html|scss|css|json|md|yml|yaml|lock)\b/i.test(e)||/\b(package\.json|package-lock\.json|node_modules|app workspace|app repository|git|github|branch|pull request|pr_url)\b/i.test(e)||/\b(npm|grep|routerlink|navtabs|typescript|angular|nodejs|codebuild|s3:\/\/)\b/i.test(e))}computeVisibleJobs(t){const e=new Set,r=[];return(t||[]).forEach(t=>{const i=this.jobDisplayKey(t);e.has(i)||(e.add(i),r.push(t))}),r}jobDisplayKey(t){const e=(Array.isArray(t?.conversation)?t.conversation:[]).find(t=>"assistant"!==String(t?.role||"").toLowerCase())?.message||"";return String(`${t?.title||""} ${e||t?.description||""}`||t?._id||"").toLowerCase().replace(/[^a-z0-9]+/g," ").trim().slice(0,180)}hasJobChanges(t){return!this.isQuestionOnlyJob(t)&&this.hasJobChangesWithoutQuestionGuard(t)}hasJobChangesWithoutQuestionGuard(t){const e=t?.artifacts?.modifiedFiles||{},r=t?.artifacts?.diffs||{};return Object.keys(e).length>0||Object.keys(r).length>0}isQuestionOnlyJob(t){return!0===t?.runPolicy?.questionOnly||String(t?.responseSummary||"").includes("AICODER_QUESTION_ONLY")||this.isLikelyQuestionChat(t)}isLikelyQuestionChat(t){if(!t||this.hasJobChangesWithoutQuestionGuard(t)||this.resolvePrUrl(t))return!1;const e=(Array.isArray(t.conversation)?t.conversation:[]).find(t=>"assistant"!==String(t?.role||"").toLowerCase())?.message||"";return this.isReadOnlyQuestion(String(e||t.description||t.title||""))}extractQuestionAnswerFromSummary(t){const e=String(t?.responseSummary||"").trim();return e.includes("AICODER_QUESTION_ONLY")?e.replace(/^AICODER_QUESTION_ONLY\s*/i,"").trim():""}isReadOnlyQuestion(t){const e=String(t||"").trim().toLowerCase();if(!e)return!1;if(/\b(fix|change|update|add|remove|delete|create|build|make|implement|deploy|merge|push|edit|replace|rename|turn on|turn off|enable|disable|set|move)\b/i.test(e))return!1;return e.includes("?")||/(^|\b)(what|why|how|where|when|who|which|explain|describe|tell me|walk me through|show me|does|do|is|are|can i|can you)\b/i.test(e)}isFailedJob(t){const e=String(t?.status||t?.phase||"").toLowerCase();return e.includes("fail")||e.includes("error")||e.includes("blocked")}runJobAction(t,e){const r=this.selectedJobId;r&&!this.isSubmitting&&(this.isSubmitting=!0,this.clearMessages(),this.api.post(this.apiBase,`/api/ai-coder/jobs/${r}/${t}`,{},this.normalizedAppToken).subscribe({next:t=>{this.successMessage=e,t?.job&&(this.mergeJob(t.job),this.selectJob(t.job))},error:e=>this.handleError(e,`Could not ${t.replace("-"," ")}.`),complete:()=>this.isSubmitting=!1}))}mergeJob(t){const e=this.normalizeJob(t);this.jobs=[e,...this.jobs.filter(t=>t._id!==e._id)],this.selectedJob=e}normalizeJob(t){return{...t,_id:String(t?._id||"").trim(),title:String(t?.title||"AI Chat").trim(),status:String(t?.status||"").trim()||"Queued"}}normalizeTab(t){return"deploy"===t||"git"===t?t:"chats"}toTitle(t){return String(t||"").replace(/[_-]+/g," ").replace(/\s+/g," ").trim().split(" ").filter(Boolean).map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")||"Waiting to start"}startAutoRefresh(){this.refreshSub?.unsubscribe(),this.autoRefresh&&(this.refreshSub=interval(15e3).subscribe(()=>{(this.jobs.some(t=>this.isActiveJob(t))||this.activeDeploys.length||this.selectedJobTestSiteUrl&&!this.qaEvidence.length)&&this.refresh()}))}clearMessages(){this.errorMessage="",this.successMessage=""}handleError(t,e){this.errorMessage=String(t?.error?.message||t?.message||e),this.successMessage="",this.isSubmitting=!1,this.isLoading=!1}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchComponent,deps:[{token:AICoderWorkbenchApiService}],target:i0.ɵɵFactoryTarget.Component});static"ɵcmp"=i0.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"21.1.2",type:AICoderWorkbenchComponent,isStandalone:!1,selector:"resolveio-client-lib-aicoder-workbench",inputs:{appId:"appId",appName:"appName",apiBase:"apiBase",appToken:"appToken",defaultTab:"defaultTab",compact:"compact",showHeader:"showHeader",autoRefresh:"autoRefresh"},usesOnChanges:!0,ngImport:i0,template:'<section class="aicoder-workbench" [class.aicoder-workbench--compact]="compact">\n\t@if (showHeader) {\n\t\t<header class="aicoder-workbench__header">\n\t\t\t<div>\n\t\t\t\t<p>AI Chats</p>\n\t\t\t\t<h2>{{ appName || \'AICoder App\' }}</h2>\n\t\t\t</div>\n\t\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh</button>\n\t\t</header>\n\t}\n\n\t@if (errorMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--error">{{ errorMessage }}</div>\n\t}\n\t@if (successMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--success">{{ successMessage }}</div>\n\t}\n\n\t<div class="aicoder-workbench__intro">\n\t\t<div>\n\t\t\t<p class="aicoder-workbench__eyebrow">AI Assistant</p>\n\t\t\t<h3>Ask for help or tell us what to change.</h3>\n\t\t\t<p>Ask questions in plain English, or request updates. AI will answer, guide you, or prepare changes when there is work to do.</p>\n\t\t</div>\n\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh Chats</button>\n\t</div>\n\n\t<div class="aicoder-workbench__layout">\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--compose">\n\t\t\t<h3>New AI Chat</h3>\n\t\t\t<input class="aicoder-workbench__input" [(ngModel)]="requestTitle" placeholder="Short name for this chat" />\n\t\t\t<textarea class="aicoder-workbench__textarea" rows="6" [(ngModel)]="requestMessage" placeholder="Ask a question, explain what is confusing, or describe what you want changed."></textarea>\n\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="submitRequest()" [disabled]="isSubmitting || !requestMessage.trim()">\n\t\t\t\tStart AI Chat\n\t\t\t</button>\n\t\t</section>\n\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--list">\n\t\t\t<div class="aicoder-workbench__panel-head">\n\t\t\t\t<h3>Your AI Chats</h3>\n\t\t\t\t<span>{{ visibleJobs.length }}</span>\n\t\t\t</div>\n\t\t\t@if (!jobs.length && !isLoading) {\n\t\t\t\t<p class="aicoder-workbench__empty">No AI chats yet.</p>\n\t\t\t}\n\t\t\t@if (isLoading && !jobs.length) {\n\t\t\t\t<p class="aicoder-workbench__empty">Loading AI chats...</p>\n\t\t\t}\n\t\t\t<div class="aicoder-workbench__chat-list">\n\t\t\t\t@for (job of visibleJobs; track job._id) {\n\t\t\t\t\t<button type="button" class="aicoder-workbench__chat" [class.is-active]="selectedJob?._id === job._id" (click)="selectJob(job)">\n\t\t\t\t\t\t<strong>{{ job.title || \'AI Chat\' }}</strong>\n\t\t\t\t\t\t<span>{{ friendlyJobStatus(job) }}</span>\n\t\t\t\t\t\t<small>{{ formatDate(job.updatedAt || job.createdAt) }}</small>\n\t\t\t\t\t</button>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</section>\n\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--detail">\n\t\t\t@if (selectedJob) {\n\t\t\t\t<div class="aicoder-workbench__detail-head">\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<p class="aicoder-workbench__eyebrow">Open Chat</p>\n\t\t\t\t\t\t<h3>{{ selectedJob.title || \'AI Chat\' }}</h3>\n\t\t\t\t\t\t<span class="aicoder-workbench__status">{{ friendlyJobStatus(selectedJob) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<button type="button" class="aicoder-workbench__danger" (click)="deleteSelectedChat()" [disabled]="isSubmitting">Delete Chat</button>\n\t\t\t\t</div>\n\n\t\t\t\t@if (selectedJobTestSiteUrl || canSendSelectedJobToTestSite || canApproveSelectedJobLive || selectedJobHasReview) {\n\t\t\t\t\t<div class="aicoder-workbench__actions" aria-label="AI chat actions">\n\t\t\t\t\t\t@if (selectedJobTestSiteUrl) {\n\t\t\t\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="openTestSite()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tOpen Test Site\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (canSendSelectedJobToTestSite) {\n\t\t\t\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendToTestSite()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tSend to Test Site\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (canApproveSelectedJobLive) {\n\t\t\t\t\t\t\t<button type="button" (click)="approveAndDeployLive()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tPut This Version Live\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (selectedJobHasReview) {\n\t\t\t\t\t\t\t<button type="button" (click)="openPr()" [disabled]="!resolvePrUrl(selectedJob)">\n\t\t\t\t\t\t\t\tOpen Review\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\t@else if (selectedJobNextStepMessage) {\n\t\t\t\t\t<div class="aicoder-workbench__next-step">\n\t\t\t\t\t\t@if (selectedJobIsWorking) {\n\t\t\t\t\t\t\t<span class="aicoder-workbench__spinner" aria-hidden="true"></span>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t<p>{{ selectedJobNextStepMessage }}</p>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\n\t\t\t\t@if (qaEvidence.length || selectedJobTestSiteUrl || isLoadingQaEvidence) {\n\t\t\t\t\t<section class="aicoder-workbench__qa">\n\t\t\t\t\t\t<div class="aicoder-workbench__qa-head">\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t<p class="aicoder-workbench__eyebrow">QA Pictures</p>\n\t\t\t\t\t\t\t\t<h4>What changed on the test site</h4>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t@if (isLoadingQaEvidence) {\n\t\t\t\t\t\t\t\t<span>Checking...</span>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t@if (qaEvidence.length) {\n\t\t\t\t\t\t\t<div class="aicoder-workbench__qa-grid">\n\t\t\t\t\t\t\t\t@for (image of qaEvidence; track image.id) {\n\t\t\t\t\t\t\t\t\t<a class="aicoder-workbench__qa-card" [href]="resolveQaEvidenceUrl(image)" target="_blank" rel="noopener">\n\t\t\t\t\t\t\t\t\t\t<img [src]="resolveQaEvidenceUrl(image)" [alt]="image.title || \'QA picture\'" loading="lazy" />\n\t\t\t\t\t\t\t\t\t\t<span>{{ image.title || \'QA picture\' }}</span>\n\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@else {\n\t\t\t\t\t\t\t<p class="aicoder-workbench__qa-empty">QA pictures will appear here after the test site finishes its visual checks.</p>\n\t\t\t\t\t\t}\n\t\t\t\t\t</section>\n\t\t\t\t}\n\n\t\t\t\t<div class="aicoder-workbench__history">\n\t\t\t\t\t@if (!jobLogs.length) {\n\t\t\t\t\t\t<div class="aicoder-workbench__thinking">\n\t\t\t\t\t\t\t<span class="aicoder-workbench__spinner" aria-hidden="true"></span>\n\t\t\t\t\t\t\t<p>AI is getting started.</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t\t@for (log of jobLogs; track log.id) {\n\t\t\t\t\t\t<div class="aicoder-workbench__message" [class.aicoder-workbench__message--user]="log.role === \'user\'" [class.aicoder-workbench__message--assistant]="log.role === \'assistant\'" [class.aicoder-workbench__message--system]="log.role === \'system\'" [class.is-active]="log.active">\n\t\t\t\t\t\t\t<div class="aicoder-workbench__message-meta">\n\t\t\t\t\t\t\t\t<strong>{{ log.label }}</strong>\n\t\t\t\t\t\t\t\t@if (log.date) {\n\t\t\t\t\t\t\t\t\t<span>{{ log.date }}</span>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<p>{{ log.message }}</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\n\t\t\t\t<label class="aicoder-workbench__reply">\n\t\t\t\t\t<span>Reply to AI</span>\n\t\t\t\t\t<textarea class="aicoder-workbench__textarea" rows="4" [(ngModel)]="chatMessage" [placeholder]="selectedJobIsQuestionOnly ? \'Ask a follow-up question.\' : \'Add more details, ask for another change, or answer a question from AI.\'"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendChatMessage()" [disabled]="!selectedJobId || !chatMessage.trim() || isSubmitting">\n\t\t\t\t\tSend Reply\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t@else {\n\t\t\t\t<div class="aicoder-workbench__blank">\n\t\t\t\t\t<h3>Select an AI chat</h3>\n\t\t\t\t\t<p>Open a chat from the list or start a new one.</p>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</section>\n\t</div>\n\n\t@if (activeDeploys.length || recentDeploys.length) {\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--updates">\n\t\t\t<div class="aicoder-workbench__panel-head">\n\t\t\t\t<h3>Website Updates</h3>\n\t\t\t\t<span>{{ activeDeploys.length ? \'Running now\' : \'Recent\' }}</span>\n\t\t\t</div>\n\t\t\t<div class="aicoder-workbench__updates">\n\t\t\t\t@for (op of activeDeploys; track op.id || op.jobId) {\n\t\t\t\t\t<div class="aicoder-workbench__update">\n\t\t\t\t\t\t<strong>{{ friendlyDeployStatus(op) }}</strong>\n\t\t\t\t\t\t<span>{{ op.backend_deploy_step || op.aws_build_current_phase || op.message || \'Working on the update\' }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\t@for (op of recentDeploys; track op.id || op.jobId) {\n\t\t\t\t\t<div class="aicoder-workbench__update">\n\t\t\t\t\t\t<strong>{{ friendlyDeployStatus(op) }}</strong>\n\t\t\t\t\t\t<span>{{ op.message || op.backend_deploy_step || \'Update finished\' }}</span>\n\t\t\t\t\t\t<small>{{ formatDate(op.updatedAt || op.createdAt) }}</small>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</section>\n\t}\n</section>\n',styles:['.aicoder-workbench{--aicoder-accent: #2f7d73;--aicoder-accent-dark: #155e57;--aicoder-border: #d8e2ec;--aicoder-ink: #1f2933;--aicoder-muted: #64748b;--aicoder-soft: #f5f8fb;display:flex;flex-direction:column;gap:1rem;color:var(--aicoder-ink)}.aicoder-workbench__header,.aicoder-workbench__intro,.aicoder-workbench__panel-head,.aicoder-workbench__detail-head,.aicoder-workbench__actions{display:flex;align-items:center;justify-content:space-between;gap:1rem}.aicoder-workbench__header p,.aicoder-workbench__eyebrow{margin:0 0 .25rem;color:var(--aicoder-accent-dark);font-size:.75rem;font-weight:800;letter-spacing:0;text-transform:uppercase}.aicoder-workbench__header h2,.aicoder-workbench__intro h3,.aicoder-workbench__panel h3{margin:0;font-weight:800;line-height:1.2}.aicoder-workbench__header h2{font-size:1.35rem}.aicoder-workbench__intro{border:1px solid var(--aicoder-border);border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__intro h3{font-size:1.25rem}.aicoder-workbench__intro p:not(.aicoder-workbench__eyebrow),.aicoder-workbench__blank p,.aicoder-workbench__empty{margin:.35rem 0 0;color:var(--aicoder-muted);line-height:1.45}.aicoder-workbench__layout{display:grid;grid-template-columns:minmax(260px,.75fr) minmax(320px,1.35fr);grid-template-areas:"compose detail" "list detail";gap:1rem;align-items:start}.aicoder-workbench__panel{min-width:0;border:1px solid var(--aicoder-border);border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__panel--compose{grid-area:compose}.aicoder-workbench__panel--list{grid-area:list}.aicoder-workbench__panel--detail{grid-area:detail}.aicoder-workbench__panel--updates{display:grid;gap:.75rem}.aicoder-workbench__panel-head span,.aicoder-workbench__status{display:inline-flex;align-items:center;border-radius:999px;background:#e7f4f0;color:var(--aicoder-accent-dark);font-size:.78rem;font-weight:800;padding:.25rem .6rem}.aicoder-workbench__next-step,.aicoder-workbench__thinking{display:flex;align-items:center;gap:.65rem;border:1px solid #dbe7ef;border-radius:8px;background:#f7fafc;color:var(--aicoder-muted);margin-top:1rem;padding:.85rem}.aicoder-workbench__next-step p,.aicoder-workbench__thinking p{margin:0;line-height:1.4}.aicoder-workbench__spinner{display:inline-block;width:1rem;height:1rem;border:2px solid #bfd2df;border-top-color:var(--aicoder-accent);border-radius:999px;flex:0 0 auto;animation:aicoder-workbench-spin .85s linear infinite}.aicoder-workbench__input,.aicoder-workbench__textarea{width:100%;border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:var(--aicoder-ink);padding:.7rem .8rem}.aicoder-workbench__textarea{resize:vertical}.aicoder-workbench__input,.aicoder-workbench__textarea{margin:.75rem 0}.aicoder-workbench__ghost,.aicoder-workbench__primary,.aicoder-workbench__danger,.aicoder-workbench__actions button{border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:var(--aicoder-ink);cursor:pointer;font:inherit;font-weight:800;min-height:40px;padding:.6rem .85rem;text-decoration:none}.aicoder-workbench__primary{border-color:var(--aicoder-accent);background:var(--aicoder-accent);color:#fff}.aicoder-workbench__danger{border-color:#fecaca;color:#991b1b}.aicoder-workbench button:disabled{cursor:not-allowed;opacity:.55}.aicoder-workbench__chat-list,.aicoder-workbench__history,.aicoder-workbench__updates{display:flex;flex-direction:column;gap:.5rem}.aicoder-workbench__chat-list,.aicoder-workbench__history{max-height:520px;overflow:auto}.aicoder-workbench__chat,.aicoder-workbench__message,.aicoder-workbench__update{display:flex;flex-direction:column;align-items:flex-start;gap:.25rem;width:100%;border:1px solid #e2e8f0;border-radius:7px;background:var(--aicoder-soft);padding:.8rem;text-align:left}.aicoder-workbench__message{background:#fff}.aicoder-workbench__message--user{border-color:#c9d8e6;background:#f8fafc}.aicoder-workbench__message--assistant{border-color:#b7ded3;background:#f4fbf9}.aicoder-workbench__message--system{background:#fbfdff}.aicoder-workbench__message.is-active{border-color:var(--aicoder-accent)}.aicoder-workbench__message-meta{display:flex;align-items:center;justify-content:space-between;gap:.75rem;width:100%;color:var(--aicoder-muted);font-size:.8rem;line-height:1.35}.aicoder-workbench__message-meta strong{color:var(--aicoder-ink);font-size:.82rem}.aicoder-workbench__chat{cursor:pointer}.aicoder-workbench__chat.is-active{border-color:var(--aicoder-accent);background:#ecfdf5}.aicoder-workbench__chat span,.aicoder-workbench__chat small,.aicoder-workbench__message span,.aicoder-workbench__update span,.aicoder-workbench__update small{color:var(--aicoder-muted);font-size:.82rem;line-height:1.35}.aicoder-workbench__message p{margin:0;white-space:pre-wrap;overflow-wrap:anywhere}.aicoder-workbench__history{min-height:260px;margin:.75rem 0}.aicoder-workbench__qa{border:1px solid #dbe7ef;border-radius:8px;background:#fbfdff;margin-top:1rem;padding:.85rem}.aicoder-workbench__qa-head{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:.75rem}.aicoder-workbench__qa-head h4{margin:0;font-size:1rem;line-height:1.25}.aicoder-workbench__qa-head span,.aicoder-workbench__qa-empty{color:var(--aicoder-muted);font-size:.82rem;line-height:1.4;margin:0}.aicoder-workbench__qa-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem}.aicoder-workbench__qa-card{display:flex;flex-direction:column;gap:.45rem;border:1px solid #dbe7ef;border-radius:7px;background:#fff;color:var(--aicoder-ink);overflow:hidden;text-decoration:none}.aicoder-workbench__qa-card img{display:block;width:100%;aspect-ratio:16/10;background:#eef3f7;object-fit:cover;object-position:top center}.aicoder-workbench__qa-card span{color:var(--aicoder-ink);font-size:.82rem;font-weight:800;line-height:1.3;padding:0 .65rem .65rem}.aicoder-workbench__actions{flex-wrap:wrap;justify-content:flex-start;margin-top:1rem}.aicoder-workbench__reply{display:block;margin-top:1rem}.aicoder-workbench__reply span{display:block;color:var(--aicoder-muted);font-size:.78rem;font-weight:800;text-transform:uppercase}.aicoder-workbench__blank{display:grid;min-height:280px;place-content:center;text-align:center}.aicoder-workbench__notice{border-radius:6px;padding:.75rem .9rem;font-weight:800}.aicoder-workbench__notice--error{border:1px solid #fecaca;background:#fff1f2;color:#991b1b}.aicoder-workbench__notice--success{border:1px solid #bbf7d0;background:#f0fdf4;color:#166534}@keyframes aicoder-workbench-spin{to{transform:rotate(360deg)}}.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:minmax(240px,.7fr) minmax(320px,1.3fr)}@media(max-width:980px){.aicoder-workbench__layout,.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:1fr;grid-template-areas:"compose" "list" "detail"}.aicoder-workbench__header,.aicoder-workbench__intro,.aicoder-workbench__detail-head{align-items:stretch;flex-direction:column}.aicoder-workbench__actions button,.aicoder-workbench__primary,.aicoder-workbench__ghost,.aicoder-workbench__danger{width:100%}}\n'],dependencies:[{kind:"directive",type:i2$1.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:i2$1.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:i2$1.NgModel,selector:"[ngModel]:not([formControlName]):not([formControl])",inputs:["name","disabled","ngModel","ngModelOptions"],outputs:["ngModelChange"],exportAs:["ngModel"]}]})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchComponent,decorators:[{type:Component,args:[{selector:"resolveio-client-lib-aicoder-workbench",standalone:!1,template:'<section class="aicoder-workbench" [class.aicoder-workbench--compact]="compact">\n\t@if (showHeader) {\n\t\t<header class="aicoder-workbench__header">\n\t\t\t<div>\n\t\t\t\t<p>AI Chats</p>\n\t\t\t\t<h2>{{ appName || \'AICoder App\' }}</h2>\n\t\t\t</div>\n\t\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh</button>\n\t\t</header>\n\t}\n\n\t@if (errorMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--error">{{ errorMessage }}</div>\n\t}\n\t@if (successMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--success">{{ successMessage }}</div>\n\t}\n\n\t<div class="aicoder-workbench__intro">\n\t\t<div>\n\t\t\t<p class="aicoder-workbench__eyebrow">AI Assistant</p>\n\t\t\t<h3>Ask for help or tell us what to change.</h3>\n\t\t\t<p>Ask questions in plain English, or request updates. AI will answer, guide you, or prepare changes when there is work to do.</p>\n\t\t</div>\n\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh Chats</button>\n\t</div>\n\n\t<div class="aicoder-workbench__layout">\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--compose">\n\t\t\t<h3>New AI Chat</h3>\n\t\t\t<input class="aicoder-workbench__input" [(ngModel)]="requestTitle" placeholder="Short name for this chat" />\n\t\t\t<textarea class="aicoder-workbench__textarea" rows="6" [(ngModel)]="requestMessage" placeholder="Ask a question, explain what is confusing, or describe what you want changed."></textarea>\n\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="submitRequest()" [disabled]="isSubmitting || !requestMessage.trim()">\n\t\t\t\tStart AI Chat\n\t\t\t</button>\n\t\t</section>\n\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--list">\n\t\t\t<div class="aicoder-workbench__panel-head">\n\t\t\t\t<h3>Your AI Chats</h3>\n\t\t\t\t<span>{{ visibleJobs.length }}</span>\n\t\t\t</div>\n\t\t\t@if (!jobs.length && !isLoading) {\n\t\t\t\t<p class="aicoder-workbench__empty">No AI chats yet.</p>\n\t\t\t}\n\t\t\t@if (isLoading && !jobs.length) {\n\t\t\t\t<p class="aicoder-workbench__empty">Loading AI chats...</p>\n\t\t\t}\n\t\t\t<div class="aicoder-workbench__chat-list">\n\t\t\t\t@for (job of visibleJobs; track job._id) {\n\t\t\t\t\t<button type="button" class="aicoder-workbench__chat" [class.is-active]="selectedJob?._id === job._id" (click)="selectJob(job)">\n\t\t\t\t\t\t<strong>{{ job.title || \'AI Chat\' }}</strong>\n\t\t\t\t\t\t<span>{{ friendlyJobStatus(job) }}</span>\n\t\t\t\t\t\t<small>{{ formatDate(job.updatedAt || job.createdAt) }}</small>\n\t\t\t\t\t</button>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</section>\n\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--detail">\n\t\t\t@if (selectedJob) {\n\t\t\t\t<div class="aicoder-workbench__detail-head">\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<p class="aicoder-workbench__eyebrow">Open Chat</p>\n\t\t\t\t\t\t<h3>{{ selectedJob.title || \'AI Chat\' }}</h3>\n\t\t\t\t\t\t<span class="aicoder-workbench__status">{{ friendlyJobStatus(selectedJob) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<button type="button" class="aicoder-workbench__danger" (click)="deleteSelectedChat()" [disabled]="isSubmitting">Delete Chat</button>\n\t\t\t\t</div>\n\n\t\t\t\t@if (selectedJobTestSiteUrl || canSendSelectedJobToTestSite || canApproveSelectedJobLive || selectedJobHasReview) {\n\t\t\t\t\t<div class="aicoder-workbench__actions" aria-label="AI chat actions">\n\t\t\t\t\t\t@if (selectedJobTestSiteUrl) {\n\t\t\t\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="openTestSite()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tOpen Test Site\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (canSendSelectedJobToTestSite) {\n\t\t\t\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendToTestSite()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tSend to Test Site\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (canApproveSelectedJobLive) {\n\t\t\t\t\t\t\t<button type="button" (click)="approveAndDeployLive()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tPut This Version Live\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (selectedJobHasReview) {\n\t\t\t\t\t\t\t<button type="button" (click)="openPr()" [disabled]="!resolvePrUrl(selectedJob)">\n\t\t\t\t\t\t\t\tOpen Review\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\t@else if (selectedJobNextStepMessage) {\n\t\t\t\t\t<div class="aicoder-workbench__next-step">\n\t\t\t\t\t\t@if (selectedJobIsWorking) {\n\t\t\t\t\t\t\t<span class="aicoder-workbench__spinner" aria-hidden="true"></span>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t<p>{{ selectedJobNextStepMessage }}</p>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\n\t\t\t\t@if (qaEvidence.length || selectedJobTestSiteUrl || isLoadingQaEvidence) {\n\t\t\t\t\t<section class="aicoder-workbench__qa">\n\t\t\t\t\t\t<div class="aicoder-workbench__qa-head">\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t<p class="aicoder-workbench__eyebrow">QA Pictures</p>\n\t\t\t\t\t\t\t\t<h4>What changed on the test site</h4>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t@if (isLoadingQaEvidence) {\n\t\t\t\t\t\t\t\t<span>Checking...</span>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t@if (qaEvidence.length) {\n\t\t\t\t\t\t\t<div class="aicoder-workbench__qa-grid">\n\t\t\t\t\t\t\t\t@for (image of qaEvidence; track image.id) {\n\t\t\t\t\t\t\t\t\t<a class="aicoder-workbench__qa-card" [href]="resolveQaEvidenceUrl(image)" target="_blank" rel="noopener">\n\t\t\t\t\t\t\t\t\t\t<img [src]="resolveQaEvidenceUrl(image)" [alt]="image.title || \'QA picture\'" loading="lazy" />\n\t\t\t\t\t\t\t\t\t\t<span>{{ image.title || \'QA picture\' }}</span>\n\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@else {\n\t\t\t\t\t\t\t<p class="aicoder-workbench__qa-empty">QA pictures will appear here after the test site finishes its visual checks.</p>\n\t\t\t\t\t\t}\n\t\t\t\t\t</section>\n\t\t\t\t}\n\n\t\t\t\t<div class="aicoder-workbench__history">\n\t\t\t\t\t@if (!jobLogs.length) {\n\t\t\t\t\t\t<div class="aicoder-workbench__thinking">\n\t\t\t\t\t\t\t<span class="aicoder-workbench__spinner" aria-hidden="true"></span>\n\t\t\t\t\t\t\t<p>AI is getting started.</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t\t@for (log of jobLogs; track log.id) {\n\t\t\t\t\t\t<div class="aicoder-workbench__message" [class.aicoder-workbench__message--user]="log.role === \'user\'" [class.aicoder-workbench__message--assistant]="log.role === \'assistant\'" [class.aicoder-workbench__message--system]="log.role === \'system\'" [class.is-active]="log.active">\n\t\t\t\t\t\t\t<div class="aicoder-workbench__message-meta">\n\t\t\t\t\t\t\t\t<strong>{{ log.label }}</strong>\n\t\t\t\t\t\t\t\t@if (log.date) {\n\t\t\t\t\t\t\t\t\t<span>{{ log.date }}</span>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<p>{{ log.message }}</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\n\t\t\t\t<label class="aicoder-workbench__reply">\n\t\t\t\t\t<span>Reply to AI</span>\n\t\t\t\t\t<textarea class="aicoder-workbench__textarea" rows="4" [(ngModel)]="chatMessage" [placeholder]="selectedJobIsQuestionOnly ? \'Ask a follow-up question.\' : \'Add more details, ask for another change, or answer a question from AI.\'"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendChatMessage()" [disabled]="!selectedJobId || !chatMessage.trim() || isSubmitting">\n\t\t\t\t\tSend Reply\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t@else {\n\t\t\t\t<div class="aicoder-workbench__blank">\n\t\t\t\t\t<h3>Select an AI chat</h3>\n\t\t\t\t\t<p>Open a chat from the list or start a new one.</p>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</section>\n\t</div>\n\n\t@if (activeDeploys.length || recentDeploys.length) {\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--updates">\n\t\t\t<div class="aicoder-workbench__panel-head">\n\t\t\t\t<h3>Website Updates</h3>\n\t\t\t\t<span>{{ activeDeploys.length ? \'Running now\' : \'Recent\' }}</span>\n\t\t\t</div>\n\t\t\t<div class="aicoder-workbench__updates">\n\t\t\t\t@for (op of activeDeploys; track op.id || op.jobId) {\n\t\t\t\t\t<div class="aicoder-workbench__update">\n\t\t\t\t\t\t<strong>{{ friendlyDeployStatus(op) }}</strong>\n\t\t\t\t\t\t<span>{{ op.backend_deploy_step || op.aws_build_current_phase || op.message || \'Working on the update\' }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\t@for (op of recentDeploys; track op.id || op.jobId) {\n\t\t\t\t\t<div class="aicoder-workbench__update">\n\t\t\t\t\t\t<strong>{{ friendlyDeployStatus(op) }}</strong>\n\t\t\t\t\t\t<span>{{ op.message || op.backend_deploy_step || \'Update finished\' }}</span>\n\t\t\t\t\t\t<small>{{ formatDate(op.updatedAt || op.createdAt) }}</small>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</section>\n\t}\n</section>\n',styles:['.aicoder-workbench{--aicoder-accent: #2f7d73;--aicoder-accent-dark: #155e57;--aicoder-border: #d8e2ec;--aicoder-ink: #1f2933;--aicoder-muted: #64748b;--aicoder-soft: #f5f8fb;display:flex;flex-direction:column;gap:1rem;color:var(--aicoder-ink)}.aicoder-workbench__header,.aicoder-workbench__intro,.aicoder-workbench__panel-head,.aicoder-workbench__detail-head,.aicoder-workbench__actions{display:flex;align-items:center;justify-content:space-between;gap:1rem}.aicoder-workbench__header p,.aicoder-workbench__eyebrow{margin:0 0 .25rem;color:var(--aicoder-accent-dark);font-size:.75rem;font-weight:800;letter-spacing:0;text-transform:uppercase}.aicoder-workbench__header h2,.aicoder-workbench__intro h3,.aicoder-workbench__panel h3{margin:0;font-weight:800;line-height:1.2}.aicoder-workbench__header h2{font-size:1.35rem}.aicoder-workbench__intro{border:1px solid var(--aicoder-border);border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__intro h3{font-size:1.25rem}.aicoder-workbench__intro p:not(.aicoder-workbench__eyebrow),.aicoder-workbench__blank p,.aicoder-workbench__empty{margin:.35rem 0 0;color:var(--aicoder-muted);line-height:1.45}.aicoder-workbench__layout{display:grid;grid-template-columns:minmax(260px,.75fr) minmax(320px,1.35fr);grid-template-areas:"compose detail" "list detail";gap:1rem;align-items:start}.aicoder-workbench__panel{min-width:0;border:1px solid var(--aicoder-border);border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__panel--compose{grid-area:compose}.aicoder-workbench__panel--list{grid-area:list}.aicoder-workbench__panel--detail{grid-area:detail}.aicoder-workbench__panel--updates{display:grid;gap:.75rem}.aicoder-workbench__panel-head span,.aicoder-workbench__status{display:inline-flex;align-items:center;border-radius:999px;background:#e7f4f0;color:var(--aicoder-accent-dark);font-size:.78rem;font-weight:800;padding:.25rem .6rem}.aicoder-workbench__next-step,.aicoder-workbench__thinking{display:flex;align-items:center;gap:.65rem;border:1px solid #dbe7ef;border-radius:8px;background:#f7fafc;color:var(--aicoder-muted);margin-top:1rem;padding:.85rem}.aicoder-workbench__next-step p,.aicoder-workbench__thinking p{margin:0;line-height:1.4}.aicoder-workbench__spinner{display:inline-block;width:1rem;height:1rem;border:2px solid #bfd2df;border-top-color:var(--aicoder-accent);border-radius:999px;flex:0 0 auto;animation:aicoder-workbench-spin .85s linear infinite}.aicoder-workbench__input,.aicoder-workbench__textarea{width:100%;border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:var(--aicoder-ink);padding:.7rem .8rem}.aicoder-workbench__textarea{resize:vertical}.aicoder-workbench__input,.aicoder-workbench__textarea{margin:.75rem 0}.aicoder-workbench__ghost,.aicoder-workbench__primary,.aicoder-workbench__danger,.aicoder-workbench__actions button{border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:var(--aicoder-ink);cursor:pointer;font:inherit;font-weight:800;min-height:40px;padding:.6rem .85rem;text-decoration:none}.aicoder-workbench__primary{border-color:var(--aicoder-accent);background:var(--aicoder-accent);color:#fff}.aicoder-workbench__danger{border-color:#fecaca;color:#991b1b}.aicoder-workbench button:disabled{cursor:not-allowed;opacity:.55}.aicoder-workbench__chat-list,.aicoder-workbench__history,.aicoder-workbench__updates{display:flex;flex-direction:column;gap:.5rem}.aicoder-workbench__chat-list,.aicoder-workbench__history{max-height:520px;overflow:auto}.aicoder-workbench__chat,.aicoder-workbench__message,.aicoder-workbench__update{display:flex;flex-direction:column;align-items:flex-start;gap:.25rem;width:100%;border:1px solid #e2e8f0;border-radius:7px;background:var(--aicoder-soft);padding:.8rem;text-align:left}.aicoder-workbench__message{background:#fff}.aicoder-workbench__message--user{border-color:#c9d8e6;background:#f8fafc}.aicoder-workbench__message--assistant{border-color:#b7ded3;background:#f4fbf9}.aicoder-workbench__message--system{background:#fbfdff}.aicoder-workbench__message.is-active{border-color:var(--aicoder-accent)}.aicoder-workbench__message-meta{display:flex;align-items:center;justify-content:space-between;gap:.75rem;width:100%;color:var(--aicoder-muted);font-size:.8rem;line-height:1.35}.aicoder-workbench__message-meta strong{color:var(--aicoder-ink);font-size:.82rem}.aicoder-workbench__chat{cursor:pointer}.aicoder-workbench__chat.is-active{border-color:var(--aicoder-accent);background:#ecfdf5}.aicoder-workbench__chat span,.aicoder-workbench__chat small,.aicoder-workbench__message span,.aicoder-workbench__update span,.aicoder-workbench__update small{color:var(--aicoder-muted);font-size:.82rem;line-height:1.35}.aicoder-workbench__message p{margin:0;white-space:pre-wrap;overflow-wrap:anywhere}.aicoder-workbench__history{min-height:260px;margin:.75rem 0}.aicoder-workbench__qa{border:1px solid #dbe7ef;border-radius:8px;background:#fbfdff;margin-top:1rem;padding:.85rem}.aicoder-workbench__qa-head{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:.75rem}.aicoder-workbench__qa-head h4{margin:0;font-size:1rem;line-height:1.25}.aicoder-workbench__qa-head span,.aicoder-workbench__qa-empty{color:var(--aicoder-muted);font-size:.82rem;line-height:1.4;margin:0}.aicoder-workbench__qa-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem}.aicoder-workbench__qa-card{display:flex;flex-direction:column;gap:.45rem;border:1px solid #dbe7ef;border-radius:7px;background:#fff;color:var(--aicoder-ink);overflow:hidden;text-decoration:none}.aicoder-workbench__qa-card img{display:block;width:100%;aspect-ratio:16/10;background:#eef3f7;object-fit:cover;object-position:top center}.aicoder-workbench__qa-card span{color:var(--aicoder-ink);font-size:.82rem;font-weight:800;line-height:1.3;padding:0 .65rem .65rem}.aicoder-workbench__actions{flex-wrap:wrap;justify-content:flex-start;margin-top:1rem}.aicoder-workbench__reply{display:block;margin-top:1rem}.aicoder-workbench__reply span{display:block;color:var(--aicoder-muted);font-size:.78rem;font-weight:800;text-transform:uppercase}.aicoder-workbench__blank{display:grid;min-height:280px;place-content:center;text-align:center}.aicoder-workbench__notice{border-radius:6px;padding:.75rem .9rem;font-weight:800}.aicoder-workbench__notice--error{border:1px solid #fecaca;background:#fff1f2;color:#991b1b}.aicoder-workbench__notice--success{border:1px solid #bbf7d0;background:#f0fdf4;color:#166534}@keyframes aicoder-workbench-spin{to{transform:rotate(360deg)}}.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:minmax(240px,.7fr) minmax(320px,1.3fr)}@media(max-width:980px){.aicoder-workbench__layout,.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:1fr;grid-template-areas:"compose" "list" "detail"}.aicoder-workbench__header,.aicoder-workbench__intro,.aicoder-workbench__detail-head{align-items:stretch;flex-direction:column}.aicoder-workbench__actions button,.aicoder-workbench__primary,.aicoder-workbench__ghost,.aicoder-workbench__danger{width:100%}}\n']}]}],ctorParameters:()=>[{type:AICoderWorkbenchApiService}],propDecorators:{appId:[{type:Input}],appName:[{type:Input}],apiBase:[{type:Input}],appToken:[{type:Input}],defaultTab:[{type:Input}],compact:[{type:Input}],showHeader:[{type:Input}],autoRefresh:[{type:Input}]}});class AICoderDashboardModule{static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,deps:[],target:i0.ɵɵFactoryTarget.NgModule});static"ɵmod"=i0.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,declarations:[AICoderDashboardComponent,AICoderWorkbenchComponent],imports:[CommonModule,FormsModule,HttpClientModule],exports:[AICoderDashboardComponent,AICoderWorkbenchComponent]});static"ɵinj"=i0.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,imports:[CommonModule,FormsModule,HttpClientModule]})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,decorators:[{type:NgModule,args:[{imports:[CommonModule,FormsModule,HttpClientModule],declarations:[AICoderDashboardComponent,AICoderWorkbenchComponent],exports:[AICoderDashboardComponent,AICoderWorkbenchComponent]}]}]});export{AICoderDashboardComponent,AICoderDashboardModule,AICoderDashboardService,AICoderWorkbenchApiService,AICoderWorkbenchComponent};
1
+ import*as i0 from"@angular/core";import{Injectable,EventEmitter,Output,Input,Component,NgModule}from"@angular/core";import*as i1 from"@angular/common/http";import{HttpHeaders,HttpClientModule}from"@angular/common/http";import*as i2 from"@resolveio/client-lib-core";import{Observable,throwError,interval}from"rxjs";import{switchMap,map,tap,shareReplay}from"rxjs/operators";import*as i2$1 from"@angular/forms";import{FormsModule}from"@angular/forms";import*as i3 from"@angular/common";import{CommonModule}from"@angular/common";class AICoderDashboardService{buildDefaultSummary(){return{overview:{app_name:"ResolveIO App",app_status:"Active",plan_tier:"small",max_users:10},database:{name:"resolveio",host:"127.0.0.1",status:"Ready"},infrastructure:{current_tier:"small",available_tiers:["small","medium","large"],max_users:10,backend_status:"Ready"},code_changes:{release_notes:"Latest updates are live and available.",last_deployed_at:"",recent_updates:["Overview tab highlights app status and plan tier.","Infrastructure tab shows Small, Medium, and Large options.","Code Changes tab summarizes customer-facing updates."]},subscription:{current_package:"Small",current_tier:"small",package_price_label:"$99 / month",billing_status:"Paid",billing_mode:"Subscription",subscription_status:"active",current_period_end:"",manage_url:"",can_downgrade:!0,downgrade_reason:""}}}buildTierOptions(){return[{id:"small",label:"Small",description:"Great for focused teams and lighter usage."},{id:"medium",label:"Medium",description:"Balanced capacity for growing teams and workflows."},{id:"large",label:"Large",description:"Higher capacity for heavy usage and larger teams."}]}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardService,deps:[],target:i0.ɵɵFactoryTarget.Injectable});static"ɵprov"=i0.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardService,providedIn:"root"})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardService,decorators:[{type:Injectable,args:[{providedIn:"root"}]}]});class AICoderDashboardComponent{dashboardService;summary;loading=!1;errorMessage="";activeTab="overview";tierOptions;tabChange=new EventEmitter;retry=new EventEmitter;tierChange=new EventEmitter;manageSubscription=new EventEmitter;constructor(t){this.dashboardService=t,this.summary=this.dashboardService.buildDefaultSummary(),this.tierOptions=this.dashboardService.buildTierOptions()}setTab(t){this.activeTab!==t&&this.tabChange.emit(t)}requestRetry(){this.retry.emit()}requestTierChange(t){this.tierChange.emit(t)}requestManageSubscription(){this.manageSubscription.emit(this.resolveManageSubscriptionUrl())}isCurrentTier(t){return String(this.summary?.infrastructure?.current_tier||"").trim().toLowerCase()===t}hasManageSubscriptionUrl(){return!!this.resolveManageSubscriptionUrl()}resolveManageSubscriptionUrl(){return String(this.summary?.subscription?.manage_url||"").trim()}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardComponent,deps:[{token:AICoderDashboardService}],target:i0.ɵɵFactoryTarget.Component});static"ɵcmp"=i0.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"21.1.2",type:AICoderDashboardComponent,isStandalone:!1,selector:"resolveio-client-lib-aicoder-dashboard",inputs:{summary:"summary",loading:"loading",errorMessage:"errorMessage",activeTab:"activeTab",tierOptions:"tierOptions"},outputs:{tabChange:"tabChange",retry:"retry",tierChange:"tierChange",manageSubscription:"manageSubscription"},ngImport:i0,template:'<div class="aicoder-dashboard">\n\t<div class="aicoder-dashboard__tabs">\n\t\t<button type="button" [class.is-active]="activeTab === \'overview\'" (click)="setTab(\'overview\')">Overview</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'database\'" (click)="setTab(\'database\')">Database</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'infrastructure\'" (click)="setTab(\'infrastructure\')">Infrastructure</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'code_changes\'" (click)="setTab(\'code_changes\')">Code Changes</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'subscription\'" (click)="setTab(\'subscription\')">Subscription</button>\n\t</div>\n\n\t@if (loading) {\n\t\t<div class="aicoder-dashboard__state">Loading dashboard details...</div>\n\t} @else if (errorMessage) {\n\t\t<div class="aicoder-dashboard__state aicoder-dashboard__state--error">\n\t\t\t<span>{{ errorMessage }}</span>\n\t\t\t<button type="button" (click)="requestRetry()">Retry</button>\n\t\t</div>\n\t} @else {\n\t\t@if (activeTab === \'overview\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>App</span><strong>{{ summary.overview.app_name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.overview.app_status }}</strong></div>\n\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.overview.plan_tier }}</strong></div>\n\t\t\t\t<div class="tile"><span>Max users</span><strong>{{ summary.overview.max_users }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'database\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>Name</span><strong>{{ summary.database.name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Host</span><strong>{{ summary.database.host }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.database.status }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'infrastructure\') {\n\t\t\t<div class="aicoder-dashboard__tiers">\n\t\t\t\t@for (tier of tierOptions; track tier.id) {\n\t\t\t\t\t<div class="tier" [class.tier--active]="isCurrentTier(tier.id)">\n\t\t\t\t\t\t<h4>{{ tier.label }}</h4>\n\t\t\t\t\t\t<p>{{ tier.description }}</p>\n\t\t\t\t\t\t<button type="button" (click)="requestTierChange(tier.id)">Choose {{ tier.label }}</button>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'code_changes\') {\n\t\t\t<div class="aicoder-dashboard__changes">\n\t\t\t\t<p>{{ summary.code_changes.release_notes }}</p>\n\t\t\t\t<ul>\n\t\t\t\t\t@for (item of summary.code_changes.recent_updates; track item) {\n\t\t\t\t\t\t<li>{{ item }}</li>\n\t\t\t\t\t}\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'subscription\') {\n\t\t\t<div class="aicoder-dashboard__subscription">\n\t\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t\t<div class="tile"><span>Package</span><strong>{{ summary.subscription?.current_package || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.subscription?.current_tier || summary.overview.plan_tier || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Price</span><strong>{{ summary.subscription?.package_price_label || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing status</span><strong>{{ summary.subscription?.billing_status || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing mode</span><strong>{{ summary.subscription?.billing_mode || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Subscription</span><strong>{{ summary.subscription?.subscription_status || \'N/A\' }}</strong></div>\n\t\t\t\t</div>\n\t\t\t\t@if (summary.subscription?.current_period_end) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint">Current period ends: {{ summary.subscription?.current_period_end }}</p>\n\t\t\t\t}\n\t\t\t\t@if (summary.subscription?.downgrade_reason) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint aicoder-dashboard__hint--warning">{{ summary.subscription?.downgrade_reason }}</p>\n\t\t\t\t}\n\t\t\t\t<div class="aicoder-dashboard__subscription-actions">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype="button"\n\t\t\t\t\t\tclass="aicoder-dashboard__primary-action"\n\t\t\t\t\t\t(click)="requestManageSubscription()"\n\t\t\t\t\t\t[disabled]="!hasManageSubscriptionUrl()"\n\t\t\t\t\t>\n\t\t\t\t\t\tUpgrade Or Manage Plan\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t}\n\t}\n</div>\n',styles:[".aicoder-dashboard{--aicoder-primary: #0f4c81;font-family:inherit}.aicoder-dashboard__tabs{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:.75rem}.aicoder-dashboard__tabs button{border:1px solid rgba(15,23,42,.2);background:#fff;padding:.45rem .75rem;border-radius:8px;font-weight:600}.aicoder-dashboard__tabs button.is-active{border-color:var(--aicoder-primary);background:#0f4c811a}.aicoder-dashboard__state{padding:.85rem;border-radius:10px;background:#f8fafc}.aicoder-dashboard__state--error{display:flex;justify-content:space-between;align-items:center;gap:.5rem;background:#fff7ed}.aicoder-dashboard__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem}.tile{padding:.8rem;border:1px solid rgba(15,23,42,.12);border-radius:10px;background:#fff}.tile span{display:block;font-size:.74rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#0f172a9e}.tile strong{display:block;margin-top:.3rem;font-size:1rem}.aicoder-dashboard__tiers{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:.75rem}.tier{padding:.85rem;border-radius:10px;border:1px solid rgba(15,23,42,.12);background:#f8fafc}.tier--active{border-color:var(--aicoder-primary);background:#0f4c8114}.tier h4{margin:0}.tier p{margin:.45rem 0}.aicoder-dashboard__changes ul{margin:0;padding-left:1.1rem}.aicoder-dashboard__subscription{display:grid;gap:.75rem}.aicoder-dashboard__subscription-actions{display:flex;flex-wrap:wrap;gap:.5rem}.aicoder-dashboard__primary-action{border:1px solid var(--aicoder-primary);background:var(--aicoder-primary);color:#fff;padding:.5rem .85rem;border-radius:8px;font-weight:700}.aicoder-dashboard__primary-action[disabled]{opacity:.55;cursor:not-allowed}.aicoder-dashboard__hint{margin:0;color:#334155;font-size:.88rem}.aicoder-dashboard__hint--warning{color:#9a3412}\n"]})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardComponent,decorators:[{type:Component,args:[{selector:"resolveio-client-lib-aicoder-dashboard",standalone:!1,template:'<div class="aicoder-dashboard">\n\t<div class="aicoder-dashboard__tabs">\n\t\t<button type="button" [class.is-active]="activeTab === \'overview\'" (click)="setTab(\'overview\')">Overview</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'database\'" (click)="setTab(\'database\')">Database</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'infrastructure\'" (click)="setTab(\'infrastructure\')">Infrastructure</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'code_changes\'" (click)="setTab(\'code_changes\')">Code Changes</button>\n\t\t<button type="button" [class.is-active]="activeTab === \'subscription\'" (click)="setTab(\'subscription\')">Subscription</button>\n\t</div>\n\n\t@if (loading) {\n\t\t<div class="aicoder-dashboard__state">Loading dashboard details...</div>\n\t} @else if (errorMessage) {\n\t\t<div class="aicoder-dashboard__state aicoder-dashboard__state--error">\n\t\t\t<span>{{ errorMessage }}</span>\n\t\t\t<button type="button" (click)="requestRetry()">Retry</button>\n\t\t</div>\n\t} @else {\n\t\t@if (activeTab === \'overview\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>App</span><strong>{{ summary.overview.app_name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.overview.app_status }}</strong></div>\n\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.overview.plan_tier }}</strong></div>\n\t\t\t\t<div class="tile"><span>Max users</span><strong>{{ summary.overview.max_users }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'database\') {\n\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t<div class="tile"><span>Name</span><strong>{{ summary.database.name }}</strong></div>\n\t\t\t\t<div class="tile"><span>Host</span><strong>{{ summary.database.host }}</strong></div>\n\t\t\t\t<div class="tile"><span>Status</span><strong>{{ summary.database.status }}</strong></div>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'infrastructure\') {\n\t\t\t<div class="aicoder-dashboard__tiers">\n\t\t\t\t@for (tier of tierOptions; track tier.id) {\n\t\t\t\t\t<div class="tier" [class.tier--active]="isCurrentTier(tier.id)">\n\t\t\t\t\t\t<h4>{{ tier.label }}</h4>\n\t\t\t\t\t\t<p>{{ tier.description }}</p>\n\t\t\t\t\t\t<button type="button" (click)="requestTierChange(tier.id)">Choose {{ tier.label }}</button>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'code_changes\') {\n\t\t\t<div class="aicoder-dashboard__changes">\n\t\t\t\t<p>{{ summary.code_changes.release_notes }}</p>\n\t\t\t\t<ul>\n\t\t\t\t\t@for (item of summary.code_changes.recent_updates; track item) {\n\t\t\t\t\t\t<li>{{ item }}</li>\n\t\t\t\t\t}\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t}\n\t\t@if (activeTab === \'subscription\') {\n\t\t\t<div class="aicoder-dashboard__subscription">\n\t\t\t\t<div class="aicoder-dashboard__grid">\n\t\t\t\t\t<div class="tile"><span>Package</span><strong>{{ summary.subscription?.current_package || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Tier</span><strong>{{ summary.subscription?.current_tier || summary.overview.plan_tier || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Price</span><strong>{{ summary.subscription?.package_price_label || \'N/A\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing status</span><strong>{{ summary.subscription?.billing_status || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Billing mode</span><strong>{{ summary.subscription?.billing_mode || \'Unknown\' }}</strong></div>\n\t\t\t\t\t<div class="tile"><span>Subscription</span><strong>{{ summary.subscription?.subscription_status || \'N/A\' }}</strong></div>\n\t\t\t\t</div>\n\t\t\t\t@if (summary.subscription?.current_period_end) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint">Current period ends: {{ summary.subscription?.current_period_end }}</p>\n\t\t\t\t}\n\t\t\t\t@if (summary.subscription?.downgrade_reason) {\n\t\t\t\t\t<p class="aicoder-dashboard__hint aicoder-dashboard__hint--warning">{{ summary.subscription?.downgrade_reason }}</p>\n\t\t\t\t}\n\t\t\t\t<div class="aicoder-dashboard__subscription-actions">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype="button"\n\t\t\t\t\t\tclass="aicoder-dashboard__primary-action"\n\t\t\t\t\t\t(click)="requestManageSubscription()"\n\t\t\t\t\t\t[disabled]="!hasManageSubscriptionUrl()"\n\t\t\t\t\t>\n\t\t\t\t\t\tUpgrade Or Manage Plan\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t}\n\t}\n</div>\n',styles:[".aicoder-dashboard{--aicoder-primary: #0f4c81;font-family:inherit}.aicoder-dashboard__tabs{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:.75rem}.aicoder-dashboard__tabs button{border:1px solid rgba(15,23,42,.2);background:#fff;padding:.45rem .75rem;border-radius:8px;font-weight:600}.aicoder-dashboard__tabs button.is-active{border-color:var(--aicoder-primary);background:#0f4c811a}.aicoder-dashboard__state{padding:.85rem;border-radius:10px;background:#f8fafc}.aicoder-dashboard__state--error{display:flex;justify-content:space-between;align-items:center;gap:.5rem;background:#fff7ed}.aicoder-dashboard__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem}.tile{padding:.8rem;border:1px solid rgba(15,23,42,.12);border-radius:10px;background:#fff}.tile span{display:block;font-size:.74rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#0f172a9e}.tile strong{display:block;margin-top:.3rem;font-size:1rem}.aicoder-dashboard__tiers{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:.75rem}.tier{padding:.85rem;border-radius:10px;border:1px solid rgba(15,23,42,.12);background:#f8fafc}.tier--active{border-color:var(--aicoder-primary);background:#0f4c8114}.tier h4{margin:0}.tier p{margin:.45rem 0}.aicoder-dashboard__changes ul{margin:0;padding-left:1.1rem}.aicoder-dashboard__subscription{display:grid;gap:.75rem}.aicoder-dashboard__subscription-actions{display:flex;flex-wrap:wrap;gap:.5rem}.aicoder-dashboard__primary-action{border:1px solid var(--aicoder-primary);background:var(--aicoder-primary);color:#fff;padding:.5rem .85rem;border-radius:8px;font-weight:700}.aicoder-dashboard__primary-action[disabled]{opacity:.55;cursor:not-allowed}.aicoder-dashboard__hint{margin:0;color:#334155;font-size:.88rem}.aicoder-dashboard__hint--warning{color:#9a3412}\n"]}]}],ctorParameters:()=>[{type:AICoderDashboardService}],propDecorators:{summary:[{type:Input}],loading:[{type:Input}],errorMessage:[{type:Input}],activeTab:[{type:Input}],tierOptions:[{type:Input}],tabChange:[{type:Output}],retry:[{type:Output}],tierChange:[{type:Output}],manageSubscription:[{type:Output}]}});class AICoderWorkbenchApiService{http;tokenManager;sessionHeaderName="X-AI-Coder-Session";appTokenHeaderName="X-AI-Coder-App-Token";sessionRefreshSkewMs=6e4;defaultSessionTtlMs=144e5;sessionByApiBase=new Map;exchangeByApiBase=new Map;constructor(t,e){this.http=t,this.tokenManager=e}get(t,e,r=""){return this.authHeaders(t,r).pipe(switchMap(r=>this.http.get(this.buildUrl(t,e),{headers:r})))}post(t,e,r={},n=""){return this.authHeaders(t,n).pipe(switchMap(n=>this.http.post(this.buildUrl(t,e),r,{headers:n})))}patch(t,e,r={},n=""){return this.authHeaders(t,n).pipe(switchMap(n=>this.http.patch(this.buildUrl(t,e),r,{headers:n})))}delete(t,e,r=""){return this.authHeaders(t,r).pipe(switchMap(r=>this.http.delete(this.buildUrl(t,e),{headers:r})))}resolveDefaultApiBase(){const t=String(window?.location?.hostname||"").trim().toLowerCase();return"aicoder.resolveio.com"===t||t.endsWith(".aicoder.resolveio.com")?"https://backend.aicoder.resolveio.com":""}authHeaders(t,e=""){const r=String(e||"").trim();return r?new Observable(t=>{t.next(new HttpHeaders({[this.appTokenHeaderName]:r})),t.complete()}):this.ensureSessionToken(t).pipe(map(t=>new HttpHeaders({[this.sessionHeaderName]:t})))}ensureSessionToken(t){const e=this.normalizeApiBase(t),r=this.sessionByApiBase.get(e);if(r&&r.token&&r.expiresAtMs>Date.now()+this.sessionRefreshSkewMs)return new Observable(t=>{t.next(r.token),t.complete()});const n=this.exchangeByApiBase.get(e);if(n)return n;const o=String(this.tokenManager.getToken("accessToken")||"").trim();if(!o)return throwError(()=>new Error("Sign in before using AICoder."));const i=new HttpHeaders({Authorization:`Bearer ${o}`}),a=this.http.post(this.buildUrl(e,"/api/ai-coder/auth/session"),{},{headers:i}).pipe(map(t=>{const r=String(t?.token||"").trim();if(!r)throw new Error("AICoder session response did not include a token.");return this.sessionByApiBase.set(e,{token:r,expiresAtMs:this.resolveSessionExpiresAtMs(t)}),r}),tap({error:()=>this.sessionByApiBase.delete(e),complete:()=>this.exchangeByApiBase.delete(e)}),shareReplay(1));return this.exchangeByApiBase.set(e,a),a}resolveSessionExpiresAtMs(t){const e=t?.expires_at?new Date(t.expires_at).getTime():0;if(Number.isFinite(e)&&e>Date.now())return e;const r=1e3*Number(t?.expires_in_seconds||0);return Date.now()+(r>0?r:this.defaultSessionTtlMs)}buildUrl(t,e){const r=this.normalizeApiBase(t),n=String(e||"").trim();return r?`${r}${n.startsWith("/")?n:`/${n}`}`:n}normalizeApiBase(t){return String(t||"").trim().replace(/\/+$/,"")}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,deps:[{token:i1.HttpClient},{token:i2.TokenManagerService}],target:i0.ɵɵFactoryTarget.Injectable});static"ɵprov"=i0.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,providedIn:"root"})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchApiService,decorators:[{type:Injectable,args:[{providedIn:"root"}]}],ctorParameters:()=>[{type:i1.HttpClient},{type:i2.TokenManagerService}]});class AICoderWorkbenchComponent{api;appId="";appName="AICoder App";apiBase="";appToken="";defaultTab="chats";compact=!1;showHeader=!0;autoRefresh=!0;activeTab="chats";runnerConsoleTab="runs";jobs=[];selectedJob=null;jobLogs=[];qaEvidence=[];deployOperations=[];deployJobs=[];gitStatus=null;isLoading=!1;isLoadingQaEvidence=!1;isSubmitting=!1;errorMessage="";successMessage="";requestTitle="";requestMessage="";chatMessage="";workflowMode="implementation";requestKind="bug_or_feature";dataChangePolicy="review_before_write";deploymentTarget="branch_pr";evidenceNotes="";qaFocus="";riskNotes="";refreshSub;constructor(t){this.api=t}ngOnInit(){this.activeTab=this.normalizeTab(this.defaultTab),this.apiBase||(this.apiBase=this.api.resolveDefaultApiBase()),this.refresh(),this.startAutoRefresh()}ngOnChanges(t){t.defaultTab&&this.defaultTab&&(this.activeTab=this.normalizeTab(this.defaultTab)),(t.appId&&!t.appId.firstChange||t.apiBase&&!t.apiBase.firstChange)&&this.refresh()}ngOnDestroy(){this.refreshSub?.unsubscribe()}setTab(t){this.activeTab=this.normalizeTab(t),"git"!==t||this.gitStatus||this.loadGitStatus(),"deploy"!==t||this.deployOperations.length||this.loadDeployHistory()}setRunnerConsoleTab(t){this.runnerConsoleTab=t}selectRunnerRun(t){this.selectJob(t),this.runnerConsoleTab="runs"}runRunnerPrimaryAction(){this.selectedJobIsWorking?this.refresh():this.selectedJobTestSiteUrl?this.openTestSite():this.canSendSelectedJobToTestSite?this.sendToTestSite():this.canApproveSelectedJobLive?this.approveAndDeployLive():this.selectedJobHasReview?this.openPr():this.refresh()}refresh(){this.normalizedAppId?(this.loadJobs(),this.loadDeployHistory(),this.loadGitStatus()):this.errorMessage="AICoder app id is missing."}selectJob(t){this.selectedJob=t,this.qaEvidence=this.extractJobQaEvidence(t),this.loadJobLogs(t._id),this.loadJobQaEvidence(t._id)}submitRequest(){if(!this.normalizedAppId||this.isSubmitting)return;const t=String(this.requestMessage||"").trim();if(!t)return void(this.errorMessage="Describe what you want changed before starting the chat.");this.isSubmitting=!0,this.clearMessages();const e=this.isReadOnlyQuestion(t),r=e?`/api/ai-coder/apps/${this.normalizedAppId}/questions`:`/api/ai-coder/apps/${this.normalizedAppId}/requests`;this.api.post(this.apiBase,r,{title:String(this.requestTitle||"").trim(),message:t,workflow_mode:this.workflowMode,request_kind:e?"question":this.requestKind,data_change_policy:this.dataChangePolicy,deployment_target:this.deploymentTarget,evidence_notes:this.evidenceNotes,qa_focus:this.qaFocus,risk_notes:this.riskNotes},this.normalizedAppToken).subscribe({next:t=>{if(this.successMessage=e?"AI answered your question.":"AI chat started.",this.requestTitle="",this.requestMessage="",t?.job){const e=this.normalizeJob(t.job);this.jobs=[e,...this.jobs.filter(t=>t._id!==e._id)],this.selectJob(e)}},error:t=>this.handleError(t,"Could not start the AI chat."),complete:()=>this.isSubmitting=!1})}sendChatMessage(){const t=this.selectedJobId,e=String(this.chatMessage||"").trim();if(!t||!e||this.isSubmitting)return;this.isSubmitting=!0,this.clearMessages();const r=this.selectedJobIsQuestionOnly?`/api/ai-coder/jobs/${t}/question`:`/api/ai-coder/jobs/${t}/prompt`;this.api.post(this.apiBase,r,{message:e},this.normalizedAppToken).subscribe({next:t=>{this.successMessage=this.selectedJobIsQuestionOnly?"AI answered your question.":"Message sent to AICoder.",this.chatMessage="",t?.job&&(this.mergeJob(t.job),this.selectJob(t.job))},error:t=>this.handleError(t,"Could not send the message."),complete:()=>this.isSubmitting=!1})}approveSelectedJob(){this.runJobAction("approve","Approved.")}createPullRequest(){this.runJobAction("create-pr","Test review started.")}publishSelectedJob(){this.runJobAction("publish","Approved for live deploy.")}sendToTestSite(){this.canSendSelectedJobToTestSite&&this.runJobAction("test-deploy","Test site is ready.")}approveAndDeployLive(){this.canApproveSelectedJobLive&&window.confirm("Put this tested version live now? The temporary test site will be removed after this starts.")&&this.runJobAction("publish","This version is being put live.")}deleteSelectedChat(){const t=this.selectedJobId;t&&!this.isSubmitting&&window.confirm("Delete this AI chat? Any temporary test site for this chat will be removed too.")&&(this.isSubmitting=!0,this.clearMessages(),this.api.delete(this.apiBase,`/api/ai-coder/jobs/${t}`,this.normalizedAppToken).subscribe({next:()=>{this.successMessage="AI chat deleted.",this.jobs=this.jobs.filter(e=>e._id!==t),this.selectedJob=this.jobs[0]||null,this.jobLogs=[],this.qaEvidence=[],this.selectedJob&&(this.loadJobLogs(this.selectedJob._id),this.loadJobQaEvidence(this.selectedJob._id))},error:t=>this.handleError(t,"Could not delete the AI chat."),complete:()=>this.isSubmitting=!1}))}queueDeploy(t){this.normalizedAppId&&!this.isSubmitting&&(this.isSubmitting=!0,this.clearMessages(),this.api.post(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/deploy`,{deploy_client:"full"===t||"client"===t,deploy_backend:"full"===t||"backend"===t,clear_destination:!0,invalidate:!0},this.normalizedAppToken).subscribe({next:()=>{this.successMessage="full"===t?"Full deploy queued.":("client"===t?"Client":"Server")+" deploy queued.",this.loadDeployHistory()},error:t=>this.handleError(t,"Could not queue deploy."),complete:()=>this.isSubmitting=!1}))}openPr(t=this.selectedJob){const e=this.resolvePrUrl(t);e&&window.open(e,"_blank","noopener")}openTestSite(){const t=this.selectedJobTestSiteUrl;t&&window.open(t,"_blank","noopener")}loadJobs(){this.normalizedAppId&&(this.isLoading=!0,this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/jobs`,this.normalizedAppToken).subscribe({next:t=>{const e=this.selectedJobId;this.jobs=(t?.jobs||[]).map(t=>this.normalizeJob(t));const r=e&&this.jobs.find(t=>t._id===e)||null;r?(this.selectedJob=r,this.loadJobLogs(r._id),this.loadJobQaEvidence(r._id)):!this.selectedJob&&this.visibleJobs.length&&this.selectJob(this.visibleJobs[0])},error:t=>this.handleError(t,"Could not load AI chats."),complete:()=>this.isLoading=!1}))}loadJobLogs(t){const e=String(t||"").trim();e?this.api.get(this.apiBase,`/api/ai-coder/jobs/${e}/logs?limit=80`,this.normalizedAppToken).subscribe({next:t=>this.jobLogs=this.buildTimeline(this.selectedJob,t?.logs||[]),error:t=>this.handleError(t,"Could not load chat history.")}):this.jobLogs=[]}loadJobQaEvidence(t){const e=String(t||"").trim();e?(this.isLoadingQaEvidence=!0,this.api.get(this.apiBase,`/api/ai-coder/jobs/${e}/qa-evidence`,this.normalizedAppToken).subscribe({next:t=>{const e=this.normalizeQaEvidenceList(t?.evidence||[]);!e.length&&this.qaEvidence.length||(this.qaEvidence=e)},error:()=>{this.isLoadingQaEvidence=!1},complete:()=>this.isLoadingQaEvidence=!1})):this.qaEvidence=[]}loadDeployHistory(){this.normalizedAppId&&this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/deploy/history`,this.normalizedAppToken).subscribe({next:t=>{this.deployOperations=t?.operations||[],this.deployJobs=t?.jobs||t?.deploy_jobs||[]},error:t=>this.handleError(t,"Could not load deploy history.")})}loadGitStatus(){this.normalizedAppId&&this.api.get(this.apiBase,`/api/ai-coder/apps/${this.normalizedAppId}/git`,this.normalizedAppToken).subscribe({next:t=>this.gitStatus=t?.status||t||null,error:t=>this.handleError(t,"Could not load git status.")})}get normalizedAppId(){return String(this.appId||"").trim()}get selectedJobId(){return String(this.selectedJob?._id||"").trim()}get normalizedAppToken(){return String(this.appToken||"").trim()}get activeDeploys(){return this.deployOperations.filter(t=>"In Progress"===String(t?.status||"").trim())}get recentDeploys(){return this.deployOperations.filter(t=>"In Progress"!==String(t?.status||"").trim()).slice(0,6)}get visibleJobs(){return this.computeVisibleJobs(this.jobs)}get runnerConsoleTabs(){return[{id:"runs",label:"Runs"},{id:"journey",label:"Journey"},{id:"qa",label:"Workflow QA"},{id:"release",label:"Release"},{id:"cost",label:"Cost"}]}get runnerRuns(){return this.visibleJobs.slice().sort((t,e)=>{const r=new Date(t.createdAt||t.updatedAt||0).getTime()||0,n=new Date(e.createdAt||e.updatedAt||0).getTime()||0;return r!==n?r-n:String(t._id||"").localeCompare(String(e._id||""))})}get selectedJobRunNumber(){const t=this.runnerRuns.findIndex(t=>t._id===this.selectedJob?._id);return t>=0?t+1:Math.max(1,this.runnerRuns.length)}get selectedJobTriggerReason(){const t=String(this.selectedJob?.lastRerunReason||"").trim();if(t)return t;const e=this.selectedJobQaRunEvidence;return e?.aiRunRetryReason?e.aiRunRetryReason:e?.aiRunFailureClass?this.toTitle(e.aiRunFailureClass):this.selectedJobRunNumber>1?"Retry or manual follow-up":"Initial app request"}get selectedJobOutcomeLabel(){const t=this.selectedJobQaRunEvidence;return t?.aiRunOutcome?this.toTitle(t.aiRunOutcome):this.selectedJobIsPublished?"Accepted":this.selectedJob?.publishLock?.active?"Release blocked":this.isFailedJob(this.selectedJob)?"Build failed":this.selectedJobHasQaRunEvidence&&t?.qaRunOutcome?this.toTitle(t.qaRunOutcome):this.friendlyJobStatus(this.selectedJob)}get selectedJobBlocker(){const t=this.selectedJobRunnerSupervisor;return String(this.selectedJobQaRunEvidence?.aiRunFailureClass||this.selectedJob?.publishLock?.reason||t.activeBlocker||t.blocker||"").trim()}get selectedJobPrimaryActionLabel(){return this.selectedJob?this.selectedJobIsWorking?"Refresh Run":this.selectedJobTestSiteUrl?"Open Test Site":this.canSendSelectedJobToTestSite?"Send to Test Site":this.canApproveSelectedJobLive?"Put Version Live":this.selectedJobHasReview?"Open Review":"Refresh":"Refresh"}get selectedJobPrimaryActionReason(){return this.selectedJob?this.selectedJobIsWorking?"The builder is still running; refresh to get the newest stage and blocker.":this.selectedJobTestSiteUrl?"A test site exists and should be walked before live release.":this.canSendSelectedJobToTestSite?"Changes exist but workflow proof should happen on a test site before release.":this.canApproveSelectedJobLive?"The update has review/test evidence and can be promoted when verified.":this.selectedJobHasReview?"A review artifact exists for this run.":this.selectedJobNextStepMessage||"Refresh the current runner state.":"No run is selected."}get selectedJobRunnerSupervisor(){return this.selectedJob?.aiCoderRunnerV6?.aiCoderV6SupervisorState||{}}get selectedJobWorkflowMemory(){return this.selectedJob?.aiCoderRunnerV6?.aiCoderV6WorkflowMemory||{}}get selectedJobBudget(){return this.selectedJob?.aiCoderRunnerV6?.aiCoderV6Budget||{}}get selectedJobStepHistory(){return Array.isArray(this.selectedJob?.aiCoderRunnerV6?.aiCoderV6StepHistory)&&this.selectedJob?.aiCoderRunnerV6?.aiCoderV6StepHistory||[]}get selectedJobWorkflowQaRows(){return Array.isArray(this.selectedJobWorkflowMemory.workflowQaRows)?this.selectedJobWorkflowMemory.workflowQaRows.slice(0,12):[]}get selectedJobCompletedWorkflowSteps(){return Array.isArray(this.selectedJobWorkflowMemory.completedWorkflowSteps)?this.selectedJobWorkflowMemory.completedWorkflowSteps.map(t=>String(t||"").trim()).filter(Boolean):[]}get selectedJobBusinessProofArtifacts(){return Array.isArray(this.selectedJobWorkflowMemory.businessProofArtifacts)?this.selectedJobWorkflowMemory.businessProofArtifacts.map(t=>String(t||"").trim()).filter(Boolean):[]}get selectedJobRunnerStages(){const t=this.selectedJobWorkflowMemory,e=this.selectedJobRunnerSupervisor,r=this.selectedJobWorkflowQaRows,n=!!t.journeyContractPath,o=this.selectedJobCompletedWorkflowSteps.length>0,i=r.some(t=>String(t?.status||"").toLowerCase().includes("pass")),a=!0===this.selectedJob?.publishLock?.active;return[{key:"journey",label:"Journey Contract",status:n?"done":"journey_contract"===e.activeStep?"active":"pending",summary:String(t.journeyContractPath||"No journey contract recorded yet.")},{key:"workflow",label:"Workflow Build",status:o?"done":"workflow_build"===e.activeStep?"active":"pending",summary:String(t.activeWorkflowStep||e.currentGoal||"North-star workflow not proven yet.")},{key:"hub",label:"Command Center",status:this.hasJobChanges(this.selectedJob)?"done":"pending",summary:this.hasJobChanges(this.selectedJob)?"App files changed in this run.":"No app changes are visible yet."},{key:"qa",label:"Workflow QA",status:i?"done":"workflow_qa"===e.activeStep?"active":"pending",summary:r.length?`${r.length} workflow QA row(s) recorded.`:"Workflow QA rows are not recorded yet."},{key:"release",label:"Publish / Deploy",status:this.selectedJobIsPublished?"done":a?"blocked":"pending",summary:a?String(this.selectedJob?.publishLock?.reason||"Publish is blocked."):this.friendlyJobStatus(this.selectedJob)}]}get selectedJobEstimatedCostUsd(){const t=this.selectedJobBudget;return Number(t.estimatedUsd||t.estimated_usd||t.currentSpendUsd||t.current_spend_usd||0)}get hasSelectedJob(){return!!this.selectedJob}get selectedJobIsWorking(){return this.isActiveJob(this.selectedJob)}get selectedJobIsQuestionOnly(){return this.isQuestionOnlyJob(this.selectedJob)}get selectedJobHasChanges(){return this.hasJobChanges(this.selectedJob)}get selectedJobHasReview(){return!!this.resolvePrUrl(this.selectedJob)}get selectedJobTestSiteUrl(){return this.resolveTestSiteUrl(this.selectedJob)}get selectedJobQaRunEvidence(){return this.selectedJob?.qaRunEvidence||this.extractJobQaRunEvidence(this.selectedJob)}get selectedJobQaRunGates(){return(this.selectedJobQaRunEvidence?.aiRunGates||[]).slice(0,6)}get selectedJobHasQaRunEvidence(){const t=this.selectedJobQaRunEvidence;return!!(t?.aiRunOutcome||t?.qaRunOutcome||t?.aiRunRetryAction||this.selectedJobQaRunGates.length)}get canSendSelectedJobToTestSite(){return!!this.selectedJobId&&!this.isSubmitting&&!this.selectedJobIsWorking&&this.selectedJobHasChanges&&!this.selectedJobTestSiteUrl&&!this.selectedJobIsPublished}get canApproveSelectedJobLive(){return!!this.selectedJobId&&!this.isSubmitting&&!this.selectedJobIsWorking&&this.selectedJobHasChanges&&!this.selectedJobIsPublished&&(!!this.selectedJobTestSiteUrl||this.selectedJobHasReview)}get selectedJobIsPublished(){return this.isPublishedJob(this.selectedJob)}get selectedJobNextStepMessage(){return this.selectedJob?this.selectedJobIsQuestionOnly?"":this.selectedJobIsWorking?"I am working on this. I will update this chat when there is something ready for you to review.":this.selectedJobTestSiteUrl?"Your test site is ready. Open it and check the change before putting it live.":this.selectedJobHasReview?"A test version is ready. Open it first, then approve it when everything looks right.":this.selectedJobIsPublished?"This update is live.":this.selectedJobHasChanges?"The update is ready. Send it to the test site when you are ready.":this.isFailedJob(this.selectedJob)?"This needs your attention before it can continue. Add a reply with what you want to try next.":"This chat will update when there is something new.":""}isActiveJob(t){if(this.isQuestionOnlyJob(t)&&"COMPLETE"===String(t?.phase||"").toUpperCase())return!1;const e=String(t?.status||"").toLowerCase(),r=String(t?.phase||t?.stage||"").toLowerCase();return!(e.includes("fail")||e.includes("error")||e.includes("complete")||e.includes("passed"))&&(!r.includes("complete")&&!r.includes("review")&&("queued"===e||"in progress"===e||"running"===e||"pending"===e||["discovery","planning","execution","linting","building"].includes(r)))}resolvePrUrl(t){return String(t?.pr_url||t?.pull_request_url||"").trim()}resolveTestSiteUrl(t){return String(t?.testSiteUrl||t?.test_website_url||t?.test_url||"").trim()}friendlyJobStatus(t){if(this.isQuestionOnlyJob(t))return this.isActiveJob(t)?"AI is answering":"Answered";const e=String(t?.status||t?.stage||"").trim().toLowerCase(),r=String(t?.phase||"").trim().toLowerCase();return e||r?e.includes("complete")||e.includes("passed")||r.includes("complete")?this.isPublishedJob(t)?"Live":this.resolveTestSiteUrl(t)?"Test site ready":this.resolvePrUrl(t)?"Ready to test":"Ready to review":e.includes("fail")||e.includes("error")?"Needs attention":e.includes("progress")||e.includes("running")||e.includes("pending")||e.includes("queued")?"AI is working":e.includes("pause")||e.includes("stopped")?"Paused":this.toTitle(e||r):"Waiting to start"}friendlyDeployStatus(t){const e=String(t?.status||"").trim(),r=String(t?.type||"").trim();if(!e&&!r)return"Website update";return`${r?this.toTitle(r.replace(/ai coder/gi,"").replace(/backend/gi,"server")):"Website update"}${e?` - ${e}`:""}`}formatDate(t){if(!t)return"";const e=t instanceof Date?t:new Date(t);return Number.isNaN(e.getTime())?String(t):e.toLocaleString()}resolveQaEvidenceUrl(t){return String(t?.url||t?.data_url||"").trim()}buildTimeline(t,e){const r=[],n=this.isQuestionOnlyJob(t),o=t=>{const e=this.sanitizeUserFacingMessage(t.message);if(!e)return;const n=`${t.role}:${t.label}:${t.date||""}:${e}`;r.some(t=>t.id===n||t.message===e)||r.push({...t,id:n,message:e})};(Array.isArray(t?.conversation)&&t?.conversation||[]).forEach((e,r)=>{const i="assistant"===String(e?.role||"").toLowerCase()?"assistant":"user",a="user"===i?this.extractUserRequest(e?.message||""):String(e?.message||"");o({role:i,label:"user"===i?"You":"Assistant",message:a,date:this.formatDate(e?.timestamp||e?.createdAt),active:!1}),0===r&&"user"===i&&this.isActiveJob(t)&&o({role:"assistant",label:"Assistant",message:n?"I am checking this and will answer without changing anything.":"I am working on this. I will update this chat when there is something ready for you to review.",date:"",active:!0})});const i=this.extractQuestionAnswerFromSummary(t);return i&&o({role:"assistant",label:"Assistant",message:i,date:this.formatDate(t?.updatedAt),active:!1}),!r.length&&this.isActiveJob(t)&&o({role:"assistant",label:"Assistant",message:n?"I am reading this and will answer shortly.":"I am working on this. I will update this chat when there is something ready for you to review.",date:"",active:!0}),r}extractUserRequest(t){const e=String(t||"").trim(),r=e.match(/(?:^|\n)Request:\s*([\s\S]*?)(?:\n\n|(?:^|\n)AICoder case runner contract:|$)/i);return r?.[1]?r[1].trim():e.split("\n").filter(t=>!t.match(/^(This is a change request|App:|Template:|App root:|AICoder|Case-building|Data safety|QA playbook|Branch, PR|Constraints:|- )/i)).join("\n").trim()}sanitizeUserFacingMessage(t){const e=String(t||"").replace(/```[\s\S]*?```/g,"[details hidden]").replace(/^\s*\[[^\]]+\]\s*/,"").replace(/\/var\/ai-workspace\/[^\s)]+/g,"the app workspace").replace(/ResolveIO\/aicoder-[\w-]+/g,"the app repository").replace(/apps\/[a-f0-9]{24}/gi,"the app").replace(/[a-f0-9]{24}/gi,"the app").replace(/\[([^\]]+)\]\([^)]+\)/g,(t,e)=>this.isInternalText(e)?"the app":e).replace(/\[([^\]]+)\]/g,(t,e)=>this.isInternalText(e)?"":e).trim();if(!e||this.isInternalOnlyMessage(e))return"";let r=!1;return e.split(/\r?\n/).map(t=>this.cleanUserFacingLine(t)).filter(t=>{const e=t.trim();return!!e&&(!this.isInternalLine(e)||!(!/^verification\s*:/i.test(e)||r)&&(r=!0,!0))}).map(t=>/^verification\s*:/i.test(t)?"I checked the update after making the change.":t).join("\n").replace(/\n{3,}/g,"\n\n").trim()}cleanUserFacingLine(t){return String(t||"").replace(/^\s*[-*]\s+/,"").replace(/^\[[^\]]+\]\s*/,"").replace(/^(targeted ui maintenance change|maintenance change|implementation update|change summary|summary)\s*/i,"").replace(/`([^`]+)`/g,(t,e)=>this.isInternalText(e)?"the app":e).replace(/\btop-nav\b/gi,"top navigation").replace(/\s+from the app\b[\s\S]*$/i,".").replace(/\s+/g," ").trim()}isInternalOnlyMessage(t){const e=String(t||"").toLowerCase();return e.includes("deterministic review")||e.includes("change request final review")||e.includes("cycle 1 summary")||e.includes("gates ")||e.includes("lint:pass")||e.includes("build:pass")||e.includes("review:pass")||e.includes("validate:pass")||e.includes("modified files captured")}isInternalLine(t){const e=String(t||"").toLowerCase();return/^verification\s*:/i.test(t)||this.isInternalText(t)||e.includes("grep")||e.includes("npm ")||e.includes("lint")||e.includes("build-dev")||e.includes("build-prod")||e.includes("workspace")||e.includes("routerlink")||e.includes("navtabs")||e.includes("fixtures")||e.includes("migrations")||e.includes("schemas")||e.includes("server flows")||e.includes("package files")||e.includes("deterministic review")||e.includes("cycle ")||e.includes("gates ")}isInternalText(t){const e=String(t||"").trim();return!!e&&(/(^|[\s"'(])(?:\.?\/)?[\w.-]+\/[\w./-]+/.test(e)||/\b[\w.-]+\.(ts|tsx|js|jsx|html|scss|css|json|md|yml|yaml|lock)\b/i.test(e)||/\b(package\.json|package-lock\.json|node_modules|app workspace|app repository|git|github|branch|pull request|pr_url)\b/i.test(e)||/\b(npm|grep|routerlink|navtabs|typescript|angular|nodejs|codebuild|s3:\/\/)\b/i.test(e))}computeVisibleJobs(t){const e=new Set,r=[];return(t||[]).forEach(t=>{const n=this.jobDisplayKey(t);e.has(n)||(e.add(n),r.push(t))}),r}jobDisplayKey(t){const e=(Array.isArray(t?.conversation)?t.conversation:[]).find(t=>"assistant"!==String(t?.role||"").toLowerCase())?.message||"";return String(`${t?.title||""} ${e||t?.description||""}`||t?._id||"").toLowerCase().replace(/[^a-z0-9]+/g," ").trim().slice(0,180)}hasJobChanges(t){return!this.isQuestionOnlyJob(t)&&this.hasJobChangesWithoutQuestionGuard(t)}hasJobChangesWithoutQuestionGuard(t){const e=t?.artifacts?.modifiedFiles||{},r=t?.artifacts?.diffs||{};return Object.keys(e).length>0||Object.keys(r).length>0}isQuestionOnlyJob(t){return!0===t?.runPolicy?.questionOnly||String(t?.responseSummary||"").includes("AICODER_QUESTION_ONLY")||this.isLikelyQuestionChat(t)}isLikelyQuestionChat(t){if(!t||this.hasJobChangesWithoutQuestionGuard(t)||this.resolvePrUrl(t))return!1;const e=(Array.isArray(t.conversation)?t.conversation:[]).find(t=>"assistant"!==String(t?.role||"").toLowerCase())?.message||"";return this.isReadOnlyQuestion(String(e||t.description||t.title||""))}extractQuestionAnswerFromSummary(t){const e=String(t?.responseSummary||"").trim();return e.includes("AICODER_QUESTION_ONLY")?e.replace(/^AICODER_QUESTION_ONLY\s*/i,"").trim():""}isReadOnlyQuestion(t){const e=String(t||"").trim().toLowerCase();if(!e)return!1;if(/\b(fix|change|update|add|remove|delete|create|build|make|implement|deploy|merge|push|edit|replace|rename|turn on|turn off|enable|disable|set|move)\b/i.test(e))return!1;return e.includes("?")||/(^|\b)(what|why|how|where|when|who|which|explain|describe|tell me|walk me through|show me|does|do|is|are|can i|can you)\b/i.test(e)}isFailedJob(t){const e=String(t?.status||t?.phase||"").toLowerCase();return e.includes("fail")||e.includes("error")||e.includes("blocked")}runJobAction(t,e){const r=this.selectedJobId;r&&!this.isSubmitting&&(this.isSubmitting=!0,this.clearMessages(),this.api.post(this.apiBase,`/api/ai-coder/jobs/${r}/${t}`,{},this.normalizedAppToken).subscribe({next:t=>{this.successMessage=e,t?.job&&(this.mergeJob(t.job),this.selectJob(t.job))},error:e=>this.handleError(e,`Could not ${t.replace("-"," ")}.`),complete:()=>this.isSubmitting=!1}))}mergeJob(t){const e=this.normalizeJob(t);this.jobs=[e,...this.jobs.filter(t=>t._id!==e._id)],this.selectedJob=e}normalizeJob(t){return{...t,_id:String(t?._id||"").trim(),title:String(t?.title||"AI Chat").trim(),status:String(t?.status||"").trim()||"Queued",qaRunEvidence:this.extractJobQaRunEvidence(t),qaEvidence:this.extractJobQaEvidence(t)}}extractJobQaEvidence(t){const e=Array.isArray(t?.qaEvidence)?t?.qaEvidence:Array.isArray(t?.qa_evidence)?t?.qa_evidence:[];return this.normalizeQaEvidenceList(e||[])}extractJobQaRunEvidence(t){if(!t)return;if(t.qaRunEvidence&&"object"==typeof t.qaRunEvidence)return t.qaRunEvidence;const e=[t.qaEvidence,t.qa_evidence];for(const t of e)if(t&&!Array.isArray(t)&&"object"==typeof t){const e=t;if(e.aiRunOutcome||e.qaRunOutcome||e.aiRunGates?.length)return{...e,aiRunGates:Array.isArray(e.aiRunGates)?e.aiRunGates:[],aiRunWarnings:Array.isArray(e.aiRunWarnings)?e.aiRunWarnings:[]}}}normalizeQaEvidenceList(t){return(t||[]).filter(t=>this.resolveQaEvidenceUrl(t))}isPublishedJob(t){const e=String(t?.pullRequestStatus||"").trim().toLowerCase();return["merged","live","published","deployed"].includes(e)}normalizeTab(t){return"deploy"===t||"git"===t?t:"chats"}toTitle(t){return String(t||"").replace(/[_-]+/g," ").replace(/\s+/g," ").trim().split(" ").filter(Boolean).map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")||"Waiting to start"}startAutoRefresh(){this.refreshSub?.unsubscribe(),this.autoRefresh&&(this.refreshSub=interval(15e3).subscribe(()=>{(this.jobs.some(t=>this.isActiveJob(t))||this.activeDeploys.length||this.selectedJobTestSiteUrl&&!this.qaEvidence.length)&&this.refresh()}))}clearMessages(){this.errorMessage="",this.successMessage=""}handleError(t,e){this.errorMessage=String(t?.error?.message||t?.message||e),this.successMessage="",this.isSubmitting=!1,this.isLoading=!1}static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchComponent,deps:[{token:AICoderWorkbenchApiService}],target:i0.ɵɵFactoryTarget.Component});static"ɵcmp"=i0.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"21.1.2",type:AICoderWorkbenchComponent,isStandalone:!1,selector:"resolveio-client-lib-aicoder-workbench",inputs:{appId:"appId",appName:"appName",apiBase:"apiBase",appToken:"appToken",defaultTab:"defaultTab",compact:"compact",showHeader:"showHeader",autoRefresh:"autoRefresh"},usesOnChanges:!0,ngImport:i0,template:'<section class="aicoder-workbench" [class.aicoder-workbench--compact]="compact">\n\t@if (showHeader) {\n\t\t<header class="aicoder-workbench__header">\n\t\t\t<div>\n\t\t\t\t<p>AI Chats</p>\n\t\t\t\t<h2>{{ appName || \'AICoder App\' }}</h2>\n\t\t\t</div>\n\t\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh</button>\n\t\t</header>\n\t}\n\n\t@if (errorMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--error">{{ errorMessage }}</div>\n\t}\n\t@if (successMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--success">{{ successMessage }}</div>\n\t}\n\n\t<section class="aicoder-workbench__runner-console">\n\t\t<div class="aicoder-workbench__runner-head">\n\t\t\t<div>\n\t\t\t\t<p class="aicoder-workbench__eyebrow">Runner Console</p>\n\t\t\t\t<h3>{{ selectedJob ? \'Run \' + selectedJobRunNumber + \': \' + (selectedJob.title || \'AI Chat\') : \'No run selected\' }}</h3>\n\t\t\t\t<p>{{ selectedJobPrimaryActionReason }}</p>\n\t\t\t</div>\n\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="runRunnerPrimaryAction()" [disabled]="isSubmitting || isLoading">\n\t\t\t\t{{ selectedJobPrimaryActionLabel }}\n\t\t\t</button>\n\t\t</div>\n\n\t\t<div class="aicoder-workbench__runner-kpis">\n\t\t\t<div>\n\t\t\t\t<span>Status</span>\n\t\t\t\t<strong>{{ friendlyJobStatus(selectedJob) }}</strong>\n\t\t\t</div>\n\t\t\t<div>\n\t\t\t\t<span>Outcome</span>\n\t\t\t\t<strong>{{ selectedJobOutcomeLabel }}</strong>\n\t\t\t</div>\n\t\t\t<div>\n\t\t\t\t<span>Blocker</span>\n\t\t\t\t<strong>{{ selectedJobBlocker || \'-\' }}</strong>\n\t\t\t</div>\n\t\t\t<div>\n\t\t\t\t<span>Cost</span>\n\t\t\t\t<strong>{{ selectedJobEstimatedCostUsd ? (selectedJobEstimatedCostUsd | currency:\'USD\':\'symbol\':\'1.2-6\') : \'-\' }}</strong>\n\t\t\t</div>\n\t\t\t<div>\n\t\t\t\t<span>Runs</span>\n\t\t\t\t<strong>{{ runnerRuns.length }}</strong>\n\t\t\t</div>\n\t\t</div>\n\n\t\t@if (runnerRuns.length) {\n\t\t\t<div class="aicoder-workbench__runner-run-tabs">\n\t\t\t\t@for (job of runnerRuns; track job._id; let i = $index) {\n\t\t\t\t\t<button type="button" [class.is-active]="selectedJob?._id === job._id" (click)="selectRunnerRun(job)">\n\t\t\t\t\t\tRun {{ i + 1 }}\n\t\t\t\t\t</button>\n\t\t\t\t}\n\t\t\t</div>\n\t\t}\n\n\t\t<div class="aicoder-workbench__runner-tabs">\n\t\t\t@for (tab of runnerConsoleTabs; track tab.id) {\n\t\t\t\t<button type="button" [class.is-active]="runnerConsoleTab === tab.id" (click)="setRunnerConsoleTab(tab.id)">\n\t\t\t\t\t{{ tab.label }}\n\t\t\t\t</button>\n\t\t\t}\n\t\t</div>\n\n\t\t@if (runnerConsoleTab === \'runs\') {\n\t\t\t<div class="aicoder-workbench__runner-stage-list">\n\t\t\t\t@for (stage of selectedJobRunnerStages; track stage.key) {\n\t\t\t\t\t<div class="aicoder-workbench__runner-stage" [class.is-done]="stage.status === \'done\'" [class.is-active]="stage.status === \'active\'" [class.is-blocked]="stage.status === \'blocked\'">\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<strong>{{ stage.label }}</strong>\n\t\t\t\t\t\t\t<span>{{ stage.status }}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<p>{{ stage.summary }}</p>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t\t@if (selectedJob) {\n\t\t\t\t<p class="aicoder-workbench__runner-note">Why this run happened: <strong>{{ selectedJobTriggerReason }}</strong></p>\n\t\t\t}\n\t\t}\n\n\t\t@if (runnerConsoleTab === \'journey\') {\n\t\t\t<div class="aicoder-workbench__runner-grid">\n\t\t\t\t<div>\n\t\t\t\t\t<span>Journey Contract</span>\n\t\t\t\t\t<strong>{{ selectedJobWorkflowMemory.journeyContractPath || \'Not recorded\' }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Primary Workflow</span>\n\t\t\t\t\t<strong>{{ selectedJobWorkflowMemory.primaryWorkflowId || \'Not selected\' }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Active Step</span>\n\t\t\t\t\t<strong>{{ selectedJobWorkflowMemory.activeWorkflowStep || selectedJobRunnerSupervisor.activeStep || \'-\' }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Blocked Step</span>\n\t\t\t\t\t<strong>{{ selectedJobWorkflowMemory.blockedWorkflowStep || \'-\' }}</strong>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t@if (selectedJobCompletedWorkflowSteps.length) {\n\t\t\t\t<p class="aicoder-workbench__runner-note">Completed: {{ selectedJobCompletedWorkflowSteps.join(\', \') }}</p>\n\t\t\t}\n\t\t}\n\n\t\t@if (runnerConsoleTab === \'qa\') {\n\t\t\t@if (selectedJobWorkflowQaRows.length) {\n\t\t\t\t<div class="aicoder-workbench__runner-qa-list">\n\t\t\t\t\t@for (row of selectedJobWorkflowQaRows; track row.stepId || row.assertion || row.route) {\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<strong>{{ row.assertion || row.action || \'Workflow assertion\' }}</strong>\n\t\t\t\t\t\t\t<span>{{ row.status || \'pending\' }}</span>\n\t\t\t\t\t\t\t<small>{{ row.route || \'\' }} {{ row.expectedState || \'\' }}</small>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t}\n\t\t\t@else {\n\t\t\t\t<p class="aicoder-workbench__empty">Workflow QA rows have not been recorded for this run yet.</p>\n\t\t\t}\n\t\t\t@if (selectedJobBusinessProofArtifacts.length) {\n\t\t\t\t<p class="aicoder-workbench__runner-note">Business proof: {{ selectedJobBusinessProofArtifacts.join(\', \') }}</p>\n\t\t\t}\n\t\t}\n\n\t\t@if (runnerConsoleTab === \'release\') {\n\t\t\t<div class="aicoder-workbench__runner-grid">\n\t\t\t\t<div>\n\t\t\t\t\t<span>Test Site</span>\n\t\t\t\t\t<strong>{{ selectedJobTestSiteUrl || \'-\' }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Review</span>\n\t\t\t\t\t<strong>{{ resolvePrUrl(selectedJob) || \'-\' }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Publish</span>\n\t\t\t\t\t<strong>{{ selectedJob?.publishLock?.active ? \'Blocked\' : friendlyJobStatus(selectedJob) }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Deploys</span>\n\t\t\t\t\t<strong>{{ activeDeploys.length ? \'Running\' : (recentDeploys.length ? \'Recent\' : \'-\') }}</strong>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t}\n\n\t\t@if (runnerConsoleTab === \'cost\') {\n\t\t\t<div class="aicoder-workbench__runner-grid">\n\t\t\t\t<div>\n\t\t\t\t\t<span>Run Cost</span>\n\t\t\t\t\t<strong>{{ selectedJobEstimatedCostUsd ? (selectedJobEstimatedCostUsd | currency:\'USD\':\'symbol\':\'1.2-6\') : \'-\' }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Budget Status</span>\n\t\t\t\t\t<strong>{{ selectedJobBudget.status || selectedJobBudget.warning || \'-\' }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Untracked Cost</span>\n\t\t\t\t\t<strong>Manual Codex sessions are separate unless logged to the usage ledger.</strong>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t}\n\t</section>\n\n\t<div class="aicoder-workbench__intro">\n\t\t<div>\n\t\t\t<p class="aicoder-workbench__eyebrow">AI Assistant</p>\n\t\t\t<h3>Ask for help or tell us what to change.</h3>\n\t\t\t<p>Ask questions in plain English, or request updates. AI will answer, guide you, or prepare changes when there is work to do.</p>\n\t\t</div>\n\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh Chats</button>\n\t</div>\n\n\t<div class="aicoder-workbench__layout">\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--compose">\n\t\t\t<h3>New AI Chat</h3>\n\t\t\t<input class="aicoder-workbench__input" [(ngModel)]="requestTitle" placeholder="Short name for this chat" />\n\t\t\t<textarea class="aicoder-workbench__textarea" rows="6" [(ngModel)]="requestMessage" placeholder="Ask a question, explain what is confusing, or describe what you want changed."></textarea>\n\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="submitRequest()" [disabled]="isSubmitting || !requestMessage.trim()">\n\t\t\t\tStart AI Chat\n\t\t\t</button>\n\t\t</section>\n\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--list">\n\t\t\t<div class="aicoder-workbench__panel-head">\n\t\t\t\t<h3>Your AI Chats</h3>\n\t\t\t\t<span>{{ visibleJobs.length }}</span>\n\t\t\t</div>\n\t\t\t@if (!jobs.length && !isLoading) {\n\t\t\t\t<p class="aicoder-workbench__empty">No AI chats yet.</p>\n\t\t\t}\n\t\t\t@if (isLoading && !jobs.length) {\n\t\t\t\t<p class="aicoder-workbench__empty">Loading AI chats...</p>\n\t\t\t}\n\t\t\t<div class="aicoder-workbench__chat-list">\n\t\t\t\t@for (job of visibleJobs; track job._id) {\n\t\t\t\t\t<button type="button" class="aicoder-workbench__chat" [class.is-active]="selectedJob?._id === job._id" (click)="selectJob(job)">\n\t\t\t\t\t\t<strong>{{ job.title || \'AI Chat\' }}</strong>\n\t\t\t\t\t\t<span>{{ friendlyJobStatus(job) }}</span>\n\t\t\t\t\t\t<small>{{ formatDate(job.updatedAt || job.createdAt) }}</small>\n\t\t\t\t\t</button>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</section>\n\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--detail">\n\t\t\t@if (selectedJob) {\n\t\t\t\t<div class="aicoder-workbench__detail-head">\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<p class="aicoder-workbench__eyebrow">Open Chat</p>\n\t\t\t\t\t\t<h3>{{ selectedJob.title || \'AI Chat\' }}</h3>\n\t\t\t\t\t\t<span class="aicoder-workbench__status">{{ friendlyJobStatus(selectedJob) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<button type="button" class="aicoder-workbench__danger" (click)="deleteSelectedChat()" [disabled]="isSubmitting">Delete Chat</button>\n\t\t\t\t</div>\n\n\t\t\t\t@if (selectedJobTestSiteUrl || canSendSelectedJobToTestSite || canApproveSelectedJobLive || selectedJobHasReview) {\n\t\t\t\t\t<div class="aicoder-workbench__actions" aria-label="AI chat actions">\n\t\t\t\t\t\t@if (selectedJobTestSiteUrl) {\n\t\t\t\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="openTestSite()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tOpen Test Site\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (canSendSelectedJobToTestSite) {\n\t\t\t\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendToTestSite()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tSend to Test Site\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (canApproveSelectedJobLive) {\n\t\t\t\t\t\t\t<button type="button" (click)="approveAndDeployLive()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tPut This Version Live\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (selectedJobHasReview) {\n\t\t\t\t\t\t\t<button type="button" (click)="openPr()" [disabled]="!resolvePrUrl(selectedJob)">\n\t\t\t\t\t\t\t\tOpen Review\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\t@else if (selectedJobNextStepMessage) {\n\t\t\t\t\t<div class="aicoder-workbench__next-step">\n\t\t\t\t\t\t@if (selectedJobIsWorking) {\n\t\t\t\t\t\t\t<span class="aicoder-workbench__spinner" aria-hidden="true"></span>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t<p>{{ selectedJobNextStepMessage }}</p>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\n\t\t\t\t@if (selectedJobHasQaRunEvidence) {\n\t\t\t\t\t<section class="aicoder-workbench__run-evidence">\n\t\t\t\t\t\t<div class="aicoder-workbench__run-evidence-head">\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t<p class="aicoder-workbench__eyebrow">AI Run Evidence</p>\n\t\t\t\t\t\t\t\t<h4>{{ selectedJobQaRunEvidence?.aiRunOutcome || \'Unknown outcome\' }}</h4>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t@if (selectedJobQaRunEvidence?.qaRunOutcome) {\n\t\t\t\t\t\t\t\t<span>{{ selectedJobQaRunEvidence?.qaRunOutcome }}</span>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t@if (selectedJobQaRunEvidence?.aiRunOutcomeReason) {\n\t\t\t\t\t\t\t<p class="aicoder-workbench__run-evidence-text">{{ selectedJobQaRunEvidence?.aiRunOutcomeReason }}</p>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (selectedJobQaRunEvidence?.aiRunNextAction) {\n\t\t\t\t\t\t\t<p class="aicoder-workbench__run-evidence-action">{{ selectedJobQaRunEvidence?.aiRunNextAction }}</p>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (selectedJobQaRunEvidence?.aiRunRetryAction || selectedJobQaRunEvidence?.aiRunFailureClass) {\n\t\t\t\t\t\t\t<div class="aicoder-workbench__retry-lane">\n\t\t\t\t\t\t\t\t<span>{{ selectedJobQaRunEvidence?.aiRunRetryAction || \'review\' }}</span>\n\t\t\t\t\t\t\t\t<strong>{{ selectedJobQaRunEvidence?.aiRunFailureClass || \'unknown\' }}</strong>\n\t\t\t\t\t\t\t\t@if (selectedJobQaRunEvidence?.aiRunRepeatedFailure) {\n\t\t\t\t\t\t\t\t\t<small>repeated</small>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (selectedJobQaRunEvidence?.aiRunRetryReason) {\n\t\t\t\t\t\t\t<p class="aicoder-workbench__run-evidence-text">{{ selectedJobQaRunEvidence?.aiRunRetryReason }}</p>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (selectedJobQaRunGates.length) {\n\t\t\t\t\t\t\t<div class="aicoder-workbench__gate-list">\n\t\t\t\t\t\t\t\t@for (gate of selectedJobQaRunGates; track gate.key || gate.label || gate.reason) {\n\t\t\t\t\t\t\t\t\t<div class="aicoder-workbench__gate" [class.is-pass]="gate.status === \'pass\'" [class.is-fail]="gate.status === \'fail\' || gate.status === \'blocked\'">\n\t\t\t\t\t\t\t\t\t\t<strong>{{ gate.label || gate.key || \'Gate\' }}</strong>\n\t\t\t\t\t\t\t\t\t\t<span>{{ gate.status || \'unknown\' }}</span>\n\t\t\t\t\t\t\t\t\t\t<small>{{ gate.reason }}</small>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t}\n\t\t\t\t\t</section>\n\t\t\t\t}\n\n\t\t\t\t@if (qaEvidence.length || selectedJobTestSiteUrl || isLoadingQaEvidence) {\n\t\t\t\t\t<section class="aicoder-workbench__qa">\n\t\t\t\t\t\t<div class="aicoder-workbench__qa-head">\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t<p class="aicoder-workbench__eyebrow">QA Pictures</p>\n\t\t\t\t\t\t\t\t<h4>What changed on the test site</h4>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t@if (isLoadingQaEvidence) {\n\t\t\t\t\t\t\t\t<span>Checking...</span>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t@if (qaEvidence.length) {\n\t\t\t\t\t\t\t<div class="aicoder-workbench__qa-grid">\n\t\t\t\t\t\t\t\t@for (image of qaEvidence; track image.id) {\n\t\t\t\t\t\t\t\t\t<a class="aicoder-workbench__qa-card" [href]="resolveQaEvidenceUrl(image)" target="_blank" rel="noopener">\n\t\t\t\t\t\t\t\t\t\t<img [src]="resolveQaEvidenceUrl(image)" [alt]="image.title || \'QA picture\'" loading="lazy" />\n\t\t\t\t\t\t\t\t\t\t<span>{{ image.title || \'QA picture\' }}</span>\n\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@else {\n\t\t\t\t\t\t\t<p class="aicoder-workbench__qa-empty">QA pictures will appear here after the test site finishes its visual checks.</p>\n\t\t\t\t\t\t}\n\t\t\t\t\t</section>\n\t\t\t\t}\n\n\t\t\t\t<div class="aicoder-workbench__history">\n\t\t\t\t\t@if (!jobLogs.length) {\n\t\t\t\t\t\t<div class="aicoder-workbench__thinking">\n\t\t\t\t\t\t\t<span class="aicoder-workbench__spinner" aria-hidden="true"></span>\n\t\t\t\t\t\t\t<p>AI is getting started.</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t\t@for (log of jobLogs; track log.id) {\n\t\t\t\t\t\t<div class="aicoder-workbench__message" [class.aicoder-workbench__message--user]="log.role === \'user\'" [class.aicoder-workbench__message--assistant]="log.role === \'assistant\'" [class.aicoder-workbench__message--system]="log.role === \'system\'" [class.is-active]="log.active">\n\t\t\t\t\t\t\t<div class="aicoder-workbench__message-meta">\n\t\t\t\t\t\t\t\t<strong>{{ log.label }}</strong>\n\t\t\t\t\t\t\t\t@if (log.date) {\n\t\t\t\t\t\t\t\t\t<span>{{ log.date }}</span>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<p>{{ log.message }}</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\n\t\t\t\t<label class="aicoder-workbench__reply">\n\t\t\t\t\t<span>Reply to AI</span>\n\t\t\t\t\t<textarea class="aicoder-workbench__textarea" rows="4" [(ngModel)]="chatMessage" [placeholder]="selectedJobIsQuestionOnly ? \'Ask a follow-up question.\' : \'Add more details, ask for another change, or answer a question from AI.\'"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendChatMessage()" [disabled]="!selectedJobId || !chatMessage.trim() || isSubmitting">\n\t\t\t\t\tSend Reply\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t@else {\n\t\t\t\t<div class="aicoder-workbench__blank">\n\t\t\t\t\t<h3>Select an AI chat</h3>\n\t\t\t\t\t<p>Open a chat from the list or start a new one.</p>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</section>\n\t</div>\n\n\t@if (activeDeploys.length || recentDeploys.length) {\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--updates">\n\t\t\t<div class="aicoder-workbench__panel-head">\n\t\t\t\t<h3>Website Updates</h3>\n\t\t\t\t<span>{{ activeDeploys.length ? \'Running now\' : \'Recent\' }}</span>\n\t\t\t</div>\n\t\t\t<div class="aicoder-workbench__updates">\n\t\t\t\t@for (op of activeDeploys; track op.id || op.jobId) {\n\t\t\t\t\t<div class="aicoder-workbench__update">\n\t\t\t\t\t\t<strong>{{ friendlyDeployStatus(op) }}</strong>\n\t\t\t\t\t\t<span>{{ op.backend_deploy_step || op.aws_build_current_phase || op.message || \'Working on the update\' }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\t@for (op of recentDeploys; track op.id || op.jobId) {\n\t\t\t\t\t<div class="aicoder-workbench__update">\n\t\t\t\t\t\t<strong>{{ friendlyDeployStatus(op) }}</strong>\n\t\t\t\t\t\t<span>{{ op.message || op.backend_deploy_step || \'Update finished\' }}</span>\n\t\t\t\t\t\t<small>{{ formatDate(op.updatedAt || op.createdAt) }}</small>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</section>\n\t}\n</section>\n',styles:['.aicoder-workbench{--aicoder-accent: #2f7d73;--aicoder-accent-dark: #155e57;--aicoder-border: #d8e2ec;--aicoder-ink: #1f2933;--aicoder-muted: #64748b;--aicoder-soft: #f5f8fb;display:flex;flex-direction:column;gap:1rem;color:var(--aicoder-ink)}.aicoder-workbench__header,.aicoder-workbench__intro,.aicoder-workbench__panel-head,.aicoder-workbench__detail-head,.aicoder-workbench__actions{display:flex;align-items:center;justify-content:space-between;gap:1rem}.aicoder-workbench__header p,.aicoder-workbench__eyebrow{margin:0 0 .25rem;color:var(--aicoder-accent-dark);font-size:.75rem;font-weight:800;letter-spacing:0;text-transform:uppercase}.aicoder-workbench__header h2,.aicoder-workbench__intro h3,.aicoder-workbench__panel h3{margin:0;font-weight:800;line-height:1.2}.aicoder-workbench__header h2{font-size:1.35rem}.aicoder-workbench__intro,.aicoder-workbench__runner-console{border:1px solid var(--aicoder-border);border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__runner-head,.aicoder-workbench__runner-kpis,.aicoder-workbench__runner-run-tabs,.aicoder-workbench__runner-tabs{display:flex;flex-wrap:wrap;gap:.75rem}.aicoder-workbench__runner-head{align-items:flex-start;justify-content:space-between;margin-bottom:.85rem}.aicoder-workbench__runner-head h3{margin:0;font-size:1.15rem;line-height:1.25}.aicoder-workbench__runner-head p:not(.aicoder-workbench__eyebrow),.aicoder-workbench__runner-note{color:var(--aicoder-muted);font-size:.86rem;line-height:1.45;margin:.35rem 0 0}.aicoder-workbench__runner-kpis{margin-bottom:.75rem}.aicoder-workbench__runner-kpis>div,.aicoder-workbench__runner-grid>div{border:1px solid #e0e8ef;border-radius:7px;background:#fbfdff;flex:1 1 150px;min-width:0;padding:.7rem}.aicoder-workbench__runner-kpis span,.aicoder-workbench__runner-grid span{display:block;color:var(--aicoder-muted);font-size:.72rem;font-weight:800;text-transform:uppercase}.aicoder-workbench__runner-kpis strong,.aicoder-workbench__runner-grid strong{display:block;color:var(--aicoder-ink);font-size:.9rem;line-height:1.35;margin-top:.2rem;overflow-wrap:anywhere}.aicoder-workbench__runner-run-tabs,.aicoder-workbench__runner-tabs{border-bottom:1px solid #dfe8f0;margin-bottom:.75rem}.aicoder-workbench__runner-run-tabs button,.aicoder-workbench__runner-tabs button{border:0;border-bottom:3px solid transparent;background:transparent;color:var(--aicoder-muted);cursor:pointer;font:inherit;font-size:.82rem;font-weight:800;padding:.45rem .6rem}.aicoder-workbench__runner-run-tabs button.is-active,.aicoder-workbench__runner-tabs button.is-active{border-bottom-color:var(--aicoder-accent);color:var(--aicoder-accent-dark)}.aicoder-workbench__runner-stage-list,.aicoder-workbench__runner-qa-list{display:grid;gap:.5rem}.aicoder-workbench__runner-stage,.aicoder-workbench__runner-qa-list>div{display:grid;grid-template-columns:minmax(150px,.4fr) minmax(0,1fr);gap:.65rem;border:1px solid #e2e8f0;border-radius:7px;background:#fbfdff;padding:.7rem}.aicoder-workbench__runner-stage div,.aicoder-workbench__runner-qa-list>div{min-width:0}.aicoder-workbench__runner-stage strong,.aicoder-workbench__runner-qa-list strong{display:block;font-size:.86rem;line-height:1.3}.aicoder-workbench__runner-stage span,.aicoder-workbench__runner-qa-list span{display:inline-flex;border-radius:999px;background:#eef2f7;color:var(--aicoder-muted);font-size:.7rem;font-weight:800;margin-top:.25rem;padding:.22rem .5rem;text-transform:uppercase}.aicoder-workbench__runner-stage p,.aicoder-workbench__runner-qa-list small{color:var(--aicoder-muted);font-size:.82rem;line-height:1.4;margin:0;overflow-wrap:anywhere}.aicoder-workbench__runner-stage.is-done span,.aicoder-workbench__runner-qa-list span{background:#e7f4f0;color:var(--aicoder-accent-dark)}.aicoder-workbench__runner-stage.is-active span{background:#eef6ff;color:#24547d}.aicoder-workbench__runner-stage.is-blocked span{background:#fff1f2;color:#991b1b}.aicoder-workbench__runner-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem}.aicoder-workbench__intro h3{font-size:1.25rem}.aicoder-workbench__intro p:not(.aicoder-workbench__eyebrow),.aicoder-workbench__blank p,.aicoder-workbench__empty{margin:.35rem 0 0;color:var(--aicoder-muted);line-height:1.45}.aicoder-workbench__layout{display:grid;grid-template-columns:minmax(260px,.75fr) minmax(320px,1.35fr);grid-template-areas:"compose detail" "list detail";gap:1rem;align-items:start}.aicoder-workbench__panel{min-width:0;border:1px solid var(--aicoder-border);border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__panel--compose{grid-area:compose}.aicoder-workbench__panel--list{grid-area:list}.aicoder-workbench__panel--detail{grid-area:detail}.aicoder-workbench__panel--updates{display:grid;gap:.75rem}.aicoder-workbench__panel-head span,.aicoder-workbench__status{display:inline-flex;align-items:center;border-radius:999px;background:#e7f4f0;color:var(--aicoder-accent-dark);font-size:.78rem;font-weight:800;padding:.25rem .6rem}.aicoder-workbench__next-step,.aicoder-workbench__thinking{display:flex;align-items:center;gap:.65rem;border:1px solid #dbe7ef;border-radius:8px;background:#f7fafc;color:var(--aicoder-muted);margin-top:1rem;padding:.85rem}.aicoder-workbench__next-step p,.aicoder-workbench__thinking p{margin:0;line-height:1.4}.aicoder-workbench__spinner{display:inline-block;width:1rem;height:1rem;border:2px solid #bfd2df;border-top-color:var(--aicoder-accent);border-radius:999px;flex:0 0 auto;animation:aicoder-workbench-spin .85s linear infinite}.aicoder-workbench__input,.aicoder-workbench__textarea{width:100%;border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:var(--aicoder-ink);padding:.7rem .8rem}.aicoder-workbench__textarea{resize:vertical}.aicoder-workbench__input,.aicoder-workbench__textarea{margin:.75rem 0}.aicoder-workbench__ghost,.aicoder-workbench__primary,.aicoder-workbench__danger,.aicoder-workbench__actions button{border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:var(--aicoder-ink);cursor:pointer;font:inherit;font-weight:800;min-height:40px;padding:.6rem .85rem;text-decoration:none}.aicoder-workbench__primary{border-color:var(--aicoder-accent);background:var(--aicoder-accent);color:#fff}.aicoder-workbench__danger{border-color:#fecaca;color:#991b1b}.aicoder-workbench button:disabled{cursor:not-allowed;opacity:.55}.aicoder-workbench__chat-list,.aicoder-workbench__history,.aicoder-workbench__updates{display:flex;flex-direction:column;gap:.5rem}.aicoder-workbench__chat-list,.aicoder-workbench__history{max-height:520px;overflow:auto}.aicoder-workbench__chat,.aicoder-workbench__message,.aicoder-workbench__update{display:flex;flex-direction:column;align-items:flex-start;gap:.25rem;width:100%;border:1px solid #e2e8f0;border-radius:7px;background:var(--aicoder-soft);padding:.8rem;text-align:left}.aicoder-workbench__message{background:#fff}.aicoder-workbench__message--user{border-color:#c9d8e6;background:#f8fafc}.aicoder-workbench__message--assistant{border-color:#b7ded3;background:#f4fbf9}.aicoder-workbench__message--system{background:#fbfdff}.aicoder-workbench__message.is-active{border-color:var(--aicoder-accent)}.aicoder-workbench__message-meta{display:flex;align-items:center;justify-content:space-between;gap:.75rem;width:100%;color:var(--aicoder-muted);font-size:.8rem;line-height:1.35}.aicoder-workbench__message-meta strong{color:var(--aicoder-ink);font-size:.82rem}.aicoder-workbench__chat{cursor:pointer}.aicoder-workbench__chat.is-active{border-color:var(--aicoder-accent);background:#ecfdf5}.aicoder-workbench__chat span,.aicoder-workbench__chat small,.aicoder-workbench__message span,.aicoder-workbench__update span,.aicoder-workbench__update small{color:var(--aicoder-muted);font-size:.82rem;line-height:1.35}.aicoder-workbench__message p{margin:0;white-space:pre-wrap;overflow-wrap:anywhere}.aicoder-workbench__history{min-height:260px;margin:.75rem 0}.aicoder-workbench__qa{border:1px solid #dbe7ef;border-radius:8px;background:#fbfdff;margin-top:1rem;padding:.85rem}.aicoder-workbench__run-evidence{border:1px solid #d7e3ee;border-radius:8px;background:#fbfcfe;margin-top:1rem;padding:.85rem}.aicoder-workbench__run-evidence-head{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}.aicoder-workbench__run-evidence-head h4{margin:0;font-size:1rem;line-height:1.25;text-transform:capitalize}.aicoder-workbench__run-evidence-head span{border-radius:999px;background:#eef6ff;color:#24547d;font-size:.78rem;font-weight:800;padding:.25rem .6rem;text-transform:capitalize}.aicoder-workbench__run-evidence-text,.aicoder-workbench__run-evidence-action{color:var(--aicoder-muted);font-size:.84rem;line-height:1.45;margin:.65rem 0 0}.aicoder-workbench__run-evidence-action{color:var(--aicoder-ink);font-weight:700}.aicoder-workbench__retry-lane{display:flex;align-items:center;flex-wrap:wrap;gap:.45rem;margin-top:.7rem}.aicoder-workbench__retry-lane span,.aicoder-workbench__retry-lane strong,.aicoder-workbench__retry-lane small{border-radius:999px;font-size:.72rem;line-height:1;padding:.38rem .5rem;text-transform:uppercase}.aicoder-workbench__retry-lane span{background:#eef6ff;color:#24547d}.aicoder-workbench__retry-lane strong{background:#f1f5f9;color:var(--aicoder-ink)}.aicoder-workbench__retry-lane small{background:#fff7ed;color:#9a3412;font-weight:700}.aicoder-workbench__gate-list{display:grid;gap:.5rem;margin-top:.75rem}.aicoder-workbench__gate{display:grid;grid-template-columns:minmax(120px,.8fr) auto;gap:.25rem .65rem;border:1px solid #e2e8f0;border-radius:7px;background:#fff;padding:.65rem}.aicoder-workbench__gate strong{font-size:.82rem;line-height:1.3}.aicoder-workbench__gate span{border-radius:999px;background:#f1f5f9;color:var(--aicoder-muted);font-size:.72rem;font-weight:800;line-height:1.2;padding:.2rem .5rem;text-transform:uppercase}.aicoder-workbench__gate small{grid-column:1/-1;color:var(--aicoder-muted);font-size:.78rem;line-height:1.35;overflow-wrap:anywhere}.aicoder-workbench__gate.is-pass span{background:#e7f4f0;color:var(--aicoder-accent-dark)}.aicoder-workbench__gate.is-fail span{background:#fff1f2;color:#991b1b}.aicoder-workbench__qa-head{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:.75rem}.aicoder-workbench__qa-head h4{margin:0;font-size:1rem;line-height:1.25}.aicoder-workbench__qa-head span,.aicoder-workbench__qa-empty{color:var(--aicoder-muted);font-size:.82rem;line-height:1.4;margin:0}.aicoder-workbench__qa-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem}.aicoder-workbench__qa-card{display:flex;flex-direction:column;gap:.45rem;border:1px solid #dbe7ef;border-radius:7px;background:#fff;color:var(--aicoder-ink);overflow:hidden;text-decoration:none}.aicoder-workbench__qa-card img{display:block;width:100%;aspect-ratio:16/10;background:#eef3f7;object-fit:cover;object-position:top center}.aicoder-workbench__qa-card span{color:var(--aicoder-ink);font-size:.82rem;font-weight:800;line-height:1.3;padding:0 .65rem .65rem}.aicoder-workbench__actions{flex-wrap:wrap;justify-content:flex-start;margin-top:1rem}.aicoder-workbench__reply{display:block;margin-top:1rem}.aicoder-workbench__reply span{display:block;color:var(--aicoder-muted);font-size:.78rem;font-weight:800;text-transform:uppercase}.aicoder-workbench__blank{display:grid;min-height:280px;place-content:center;text-align:center}.aicoder-workbench__notice{border-radius:6px;padding:.75rem .9rem;font-weight:800}.aicoder-workbench__notice--error{border:1px solid #fecaca;background:#fff1f2;color:#991b1b}.aicoder-workbench__notice--success{border:1px solid #bbf7d0;background:#f0fdf4;color:#166534}@keyframes aicoder-workbench-spin{to{transform:rotate(360deg)}}.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:minmax(240px,.7fr) minmax(320px,1.3fr)}@media(max-width:980px){.aicoder-workbench__layout,.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:1fr;grid-template-areas:"compose" "list" "detail"}.aicoder-workbench__header,.aicoder-workbench__intro,.aicoder-workbench__detail-head{align-items:stretch;flex-direction:column}.aicoder-workbench__actions button,.aicoder-workbench__primary,.aicoder-workbench__ghost,.aicoder-workbench__danger{width:100%}}\n'],dependencies:[{kind:"directive",type:i2$1.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:i2$1.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:i2$1.NgModel,selector:"[ngModel]:not([formControlName]):not([formControl])",inputs:["name","disabled","ngModel","ngModelOptions"],outputs:["ngModelChange"],exportAs:["ngModel"]},{kind:"pipe",type:i3.CurrencyPipe,name:"currency"}]})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderWorkbenchComponent,decorators:[{type:Component,args:[{selector:"resolveio-client-lib-aicoder-workbench",standalone:!1,template:'<section class="aicoder-workbench" [class.aicoder-workbench--compact]="compact">\n\t@if (showHeader) {\n\t\t<header class="aicoder-workbench__header">\n\t\t\t<div>\n\t\t\t\t<p>AI Chats</p>\n\t\t\t\t<h2>{{ appName || \'AICoder App\' }}</h2>\n\t\t\t</div>\n\t\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh</button>\n\t\t</header>\n\t}\n\n\t@if (errorMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--error">{{ errorMessage }}</div>\n\t}\n\t@if (successMessage) {\n\t\t<div class="aicoder-workbench__notice aicoder-workbench__notice--success">{{ successMessage }}</div>\n\t}\n\n\t<section class="aicoder-workbench__runner-console">\n\t\t<div class="aicoder-workbench__runner-head">\n\t\t\t<div>\n\t\t\t\t<p class="aicoder-workbench__eyebrow">Runner Console</p>\n\t\t\t\t<h3>{{ selectedJob ? \'Run \' + selectedJobRunNumber + \': \' + (selectedJob.title || \'AI Chat\') : \'No run selected\' }}</h3>\n\t\t\t\t<p>{{ selectedJobPrimaryActionReason }}</p>\n\t\t\t</div>\n\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="runRunnerPrimaryAction()" [disabled]="isSubmitting || isLoading">\n\t\t\t\t{{ selectedJobPrimaryActionLabel }}\n\t\t\t</button>\n\t\t</div>\n\n\t\t<div class="aicoder-workbench__runner-kpis">\n\t\t\t<div>\n\t\t\t\t<span>Status</span>\n\t\t\t\t<strong>{{ friendlyJobStatus(selectedJob) }}</strong>\n\t\t\t</div>\n\t\t\t<div>\n\t\t\t\t<span>Outcome</span>\n\t\t\t\t<strong>{{ selectedJobOutcomeLabel }}</strong>\n\t\t\t</div>\n\t\t\t<div>\n\t\t\t\t<span>Blocker</span>\n\t\t\t\t<strong>{{ selectedJobBlocker || \'-\' }}</strong>\n\t\t\t</div>\n\t\t\t<div>\n\t\t\t\t<span>Cost</span>\n\t\t\t\t<strong>{{ selectedJobEstimatedCostUsd ? (selectedJobEstimatedCostUsd | currency:\'USD\':\'symbol\':\'1.2-6\') : \'-\' }}</strong>\n\t\t\t</div>\n\t\t\t<div>\n\t\t\t\t<span>Runs</span>\n\t\t\t\t<strong>{{ runnerRuns.length }}</strong>\n\t\t\t</div>\n\t\t</div>\n\n\t\t@if (runnerRuns.length) {\n\t\t\t<div class="aicoder-workbench__runner-run-tabs">\n\t\t\t\t@for (job of runnerRuns; track job._id; let i = $index) {\n\t\t\t\t\t<button type="button" [class.is-active]="selectedJob?._id === job._id" (click)="selectRunnerRun(job)">\n\t\t\t\t\t\tRun {{ i + 1 }}\n\t\t\t\t\t</button>\n\t\t\t\t}\n\t\t\t</div>\n\t\t}\n\n\t\t<div class="aicoder-workbench__runner-tabs">\n\t\t\t@for (tab of runnerConsoleTabs; track tab.id) {\n\t\t\t\t<button type="button" [class.is-active]="runnerConsoleTab === tab.id" (click)="setRunnerConsoleTab(tab.id)">\n\t\t\t\t\t{{ tab.label }}\n\t\t\t\t</button>\n\t\t\t}\n\t\t</div>\n\n\t\t@if (runnerConsoleTab === \'runs\') {\n\t\t\t<div class="aicoder-workbench__runner-stage-list">\n\t\t\t\t@for (stage of selectedJobRunnerStages; track stage.key) {\n\t\t\t\t\t<div class="aicoder-workbench__runner-stage" [class.is-done]="stage.status === \'done\'" [class.is-active]="stage.status === \'active\'" [class.is-blocked]="stage.status === \'blocked\'">\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<strong>{{ stage.label }}</strong>\n\t\t\t\t\t\t\t<span>{{ stage.status }}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<p>{{ stage.summary }}</p>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t\t@if (selectedJob) {\n\t\t\t\t<p class="aicoder-workbench__runner-note">Why this run happened: <strong>{{ selectedJobTriggerReason }}</strong></p>\n\t\t\t}\n\t\t}\n\n\t\t@if (runnerConsoleTab === \'journey\') {\n\t\t\t<div class="aicoder-workbench__runner-grid">\n\t\t\t\t<div>\n\t\t\t\t\t<span>Journey Contract</span>\n\t\t\t\t\t<strong>{{ selectedJobWorkflowMemory.journeyContractPath || \'Not recorded\' }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Primary Workflow</span>\n\t\t\t\t\t<strong>{{ selectedJobWorkflowMemory.primaryWorkflowId || \'Not selected\' }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Active Step</span>\n\t\t\t\t\t<strong>{{ selectedJobWorkflowMemory.activeWorkflowStep || selectedJobRunnerSupervisor.activeStep || \'-\' }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Blocked Step</span>\n\t\t\t\t\t<strong>{{ selectedJobWorkflowMemory.blockedWorkflowStep || \'-\' }}</strong>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t@if (selectedJobCompletedWorkflowSteps.length) {\n\t\t\t\t<p class="aicoder-workbench__runner-note">Completed: {{ selectedJobCompletedWorkflowSteps.join(\', \') }}</p>\n\t\t\t}\n\t\t}\n\n\t\t@if (runnerConsoleTab === \'qa\') {\n\t\t\t@if (selectedJobWorkflowQaRows.length) {\n\t\t\t\t<div class="aicoder-workbench__runner-qa-list">\n\t\t\t\t\t@for (row of selectedJobWorkflowQaRows; track row.stepId || row.assertion || row.route) {\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<strong>{{ row.assertion || row.action || \'Workflow assertion\' }}</strong>\n\t\t\t\t\t\t\t<span>{{ row.status || \'pending\' }}</span>\n\t\t\t\t\t\t\t<small>{{ row.route || \'\' }} {{ row.expectedState || \'\' }}</small>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t}\n\t\t\t@else {\n\t\t\t\t<p class="aicoder-workbench__empty">Workflow QA rows have not been recorded for this run yet.</p>\n\t\t\t}\n\t\t\t@if (selectedJobBusinessProofArtifacts.length) {\n\t\t\t\t<p class="aicoder-workbench__runner-note">Business proof: {{ selectedJobBusinessProofArtifacts.join(\', \') }}</p>\n\t\t\t}\n\t\t}\n\n\t\t@if (runnerConsoleTab === \'release\') {\n\t\t\t<div class="aicoder-workbench__runner-grid">\n\t\t\t\t<div>\n\t\t\t\t\t<span>Test Site</span>\n\t\t\t\t\t<strong>{{ selectedJobTestSiteUrl || \'-\' }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Review</span>\n\t\t\t\t\t<strong>{{ resolvePrUrl(selectedJob) || \'-\' }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Publish</span>\n\t\t\t\t\t<strong>{{ selectedJob?.publishLock?.active ? \'Blocked\' : friendlyJobStatus(selectedJob) }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Deploys</span>\n\t\t\t\t\t<strong>{{ activeDeploys.length ? \'Running\' : (recentDeploys.length ? \'Recent\' : \'-\') }}</strong>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t}\n\n\t\t@if (runnerConsoleTab === \'cost\') {\n\t\t\t<div class="aicoder-workbench__runner-grid">\n\t\t\t\t<div>\n\t\t\t\t\t<span>Run Cost</span>\n\t\t\t\t\t<strong>{{ selectedJobEstimatedCostUsd ? (selectedJobEstimatedCostUsd | currency:\'USD\':\'symbol\':\'1.2-6\') : \'-\' }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Budget Status</span>\n\t\t\t\t\t<strong>{{ selectedJobBudget.status || selectedJobBudget.warning || \'-\' }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<span>Untracked Cost</span>\n\t\t\t\t\t<strong>Manual Codex sessions are separate unless logged to the usage ledger.</strong>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t}\n\t</section>\n\n\t<div class="aicoder-workbench__intro">\n\t\t<div>\n\t\t\t<p class="aicoder-workbench__eyebrow">AI Assistant</p>\n\t\t\t<h3>Ask for help or tell us what to change.</h3>\n\t\t\t<p>Ask questions in plain English, or request updates. AI will answer, guide you, or prepare changes when there is work to do.</p>\n\t\t</div>\n\t\t<button type="button" class="aicoder-workbench__ghost" (click)="refresh()" [disabled]="isLoading || isSubmitting">Refresh Chats</button>\n\t</div>\n\n\t<div class="aicoder-workbench__layout">\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--compose">\n\t\t\t<h3>New AI Chat</h3>\n\t\t\t<input class="aicoder-workbench__input" [(ngModel)]="requestTitle" placeholder="Short name for this chat" />\n\t\t\t<textarea class="aicoder-workbench__textarea" rows="6" [(ngModel)]="requestMessage" placeholder="Ask a question, explain what is confusing, or describe what you want changed."></textarea>\n\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="submitRequest()" [disabled]="isSubmitting || !requestMessage.trim()">\n\t\t\t\tStart AI Chat\n\t\t\t</button>\n\t\t</section>\n\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--list">\n\t\t\t<div class="aicoder-workbench__panel-head">\n\t\t\t\t<h3>Your AI Chats</h3>\n\t\t\t\t<span>{{ visibleJobs.length }}</span>\n\t\t\t</div>\n\t\t\t@if (!jobs.length && !isLoading) {\n\t\t\t\t<p class="aicoder-workbench__empty">No AI chats yet.</p>\n\t\t\t}\n\t\t\t@if (isLoading && !jobs.length) {\n\t\t\t\t<p class="aicoder-workbench__empty">Loading AI chats...</p>\n\t\t\t}\n\t\t\t<div class="aicoder-workbench__chat-list">\n\t\t\t\t@for (job of visibleJobs; track job._id) {\n\t\t\t\t\t<button type="button" class="aicoder-workbench__chat" [class.is-active]="selectedJob?._id === job._id" (click)="selectJob(job)">\n\t\t\t\t\t\t<strong>{{ job.title || \'AI Chat\' }}</strong>\n\t\t\t\t\t\t<span>{{ friendlyJobStatus(job) }}</span>\n\t\t\t\t\t\t<small>{{ formatDate(job.updatedAt || job.createdAt) }}</small>\n\t\t\t\t\t</button>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</section>\n\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--detail">\n\t\t\t@if (selectedJob) {\n\t\t\t\t<div class="aicoder-workbench__detail-head">\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<p class="aicoder-workbench__eyebrow">Open Chat</p>\n\t\t\t\t\t\t<h3>{{ selectedJob.title || \'AI Chat\' }}</h3>\n\t\t\t\t\t\t<span class="aicoder-workbench__status">{{ friendlyJobStatus(selectedJob) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<button type="button" class="aicoder-workbench__danger" (click)="deleteSelectedChat()" [disabled]="isSubmitting">Delete Chat</button>\n\t\t\t\t</div>\n\n\t\t\t\t@if (selectedJobTestSiteUrl || canSendSelectedJobToTestSite || canApproveSelectedJobLive || selectedJobHasReview) {\n\t\t\t\t\t<div class="aicoder-workbench__actions" aria-label="AI chat actions">\n\t\t\t\t\t\t@if (selectedJobTestSiteUrl) {\n\t\t\t\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="openTestSite()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tOpen Test Site\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (canSendSelectedJobToTestSite) {\n\t\t\t\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendToTestSite()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tSend to Test Site\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (canApproveSelectedJobLive) {\n\t\t\t\t\t\t\t<button type="button" (click)="approveAndDeployLive()" [disabled]="isSubmitting">\n\t\t\t\t\t\t\t\tPut This Version Live\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (selectedJobHasReview) {\n\t\t\t\t\t\t\t<button type="button" (click)="openPr()" [disabled]="!resolvePrUrl(selectedJob)">\n\t\t\t\t\t\t\t\tOpen Review\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\t@else if (selectedJobNextStepMessage) {\n\t\t\t\t\t<div class="aicoder-workbench__next-step">\n\t\t\t\t\t\t@if (selectedJobIsWorking) {\n\t\t\t\t\t\t\t<span class="aicoder-workbench__spinner" aria-hidden="true"></span>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t<p>{{ selectedJobNextStepMessage }}</p>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\n\t\t\t\t@if (selectedJobHasQaRunEvidence) {\n\t\t\t\t\t<section class="aicoder-workbench__run-evidence">\n\t\t\t\t\t\t<div class="aicoder-workbench__run-evidence-head">\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t<p class="aicoder-workbench__eyebrow">AI Run Evidence</p>\n\t\t\t\t\t\t\t\t<h4>{{ selectedJobQaRunEvidence?.aiRunOutcome || \'Unknown outcome\' }}</h4>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t@if (selectedJobQaRunEvidence?.qaRunOutcome) {\n\t\t\t\t\t\t\t\t<span>{{ selectedJobQaRunEvidence?.qaRunOutcome }}</span>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t@if (selectedJobQaRunEvidence?.aiRunOutcomeReason) {\n\t\t\t\t\t\t\t<p class="aicoder-workbench__run-evidence-text">{{ selectedJobQaRunEvidence?.aiRunOutcomeReason }}</p>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (selectedJobQaRunEvidence?.aiRunNextAction) {\n\t\t\t\t\t\t\t<p class="aicoder-workbench__run-evidence-action">{{ selectedJobQaRunEvidence?.aiRunNextAction }}</p>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (selectedJobQaRunEvidence?.aiRunRetryAction || selectedJobQaRunEvidence?.aiRunFailureClass) {\n\t\t\t\t\t\t\t<div class="aicoder-workbench__retry-lane">\n\t\t\t\t\t\t\t\t<span>{{ selectedJobQaRunEvidence?.aiRunRetryAction || \'review\' }}</span>\n\t\t\t\t\t\t\t\t<strong>{{ selectedJobQaRunEvidence?.aiRunFailureClass || \'unknown\' }}</strong>\n\t\t\t\t\t\t\t\t@if (selectedJobQaRunEvidence?.aiRunRepeatedFailure) {\n\t\t\t\t\t\t\t\t\t<small>repeated</small>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (selectedJobQaRunEvidence?.aiRunRetryReason) {\n\t\t\t\t\t\t\t<p class="aicoder-workbench__run-evidence-text">{{ selectedJobQaRunEvidence?.aiRunRetryReason }}</p>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@if (selectedJobQaRunGates.length) {\n\t\t\t\t\t\t\t<div class="aicoder-workbench__gate-list">\n\t\t\t\t\t\t\t\t@for (gate of selectedJobQaRunGates; track gate.key || gate.label || gate.reason) {\n\t\t\t\t\t\t\t\t\t<div class="aicoder-workbench__gate" [class.is-pass]="gate.status === \'pass\'" [class.is-fail]="gate.status === \'fail\' || gate.status === \'blocked\'">\n\t\t\t\t\t\t\t\t\t\t<strong>{{ gate.label || gate.key || \'Gate\' }}</strong>\n\t\t\t\t\t\t\t\t\t\t<span>{{ gate.status || \'unknown\' }}</span>\n\t\t\t\t\t\t\t\t\t\t<small>{{ gate.reason }}</small>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t}\n\t\t\t\t\t</section>\n\t\t\t\t}\n\n\t\t\t\t@if (qaEvidence.length || selectedJobTestSiteUrl || isLoadingQaEvidence) {\n\t\t\t\t\t<section class="aicoder-workbench__qa">\n\t\t\t\t\t\t<div class="aicoder-workbench__qa-head">\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t<p class="aicoder-workbench__eyebrow">QA Pictures</p>\n\t\t\t\t\t\t\t\t<h4>What changed on the test site</h4>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t@if (isLoadingQaEvidence) {\n\t\t\t\t\t\t\t\t<span>Checking...</span>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t@if (qaEvidence.length) {\n\t\t\t\t\t\t\t<div class="aicoder-workbench__qa-grid">\n\t\t\t\t\t\t\t\t@for (image of qaEvidence; track image.id) {\n\t\t\t\t\t\t\t\t\t<a class="aicoder-workbench__qa-card" [href]="resolveQaEvidenceUrl(image)" target="_blank" rel="noopener">\n\t\t\t\t\t\t\t\t\t\t<img [src]="resolveQaEvidenceUrl(image)" [alt]="image.title || \'QA picture\'" loading="lazy" />\n\t\t\t\t\t\t\t\t\t\t<span>{{ image.title || \'QA picture\' }}</span>\n\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@else {\n\t\t\t\t\t\t\t<p class="aicoder-workbench__qa-empty">QA pictures will appear here after the test site finishes its visual checks.</p>\n\t\t\t\t\t\t}\n\t\t\t\t\t</section>\n\t\t\t\t}\n\n\t\t\t\t<div class="aicoder-workbench__history">\n\t\t\t\t\t@if (!jobLogs.length) {\n\t\t\t\t\t\t<div class="aicoder-workbench__thinking">\n\t\t\t\t\t\t\t<span class="aicoder-workbench__spinner" aria-hidden="true"></span>\n\t\t\t\t\t\t\t<p>AI is getting started.</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t\t@for (log of jobLogs; track log.id) {\n\t\t\t\t\t\t<div class="aicoder-workbench__message" [class.aicoder-workbench__message--user]="log.role === \'user\'" [class.aicoder-workbench__message--assistant]="log.role === \'assistant\'" [class.aicoder-workbench__message--system]="log.role === \'system\'" [class.is-active]="log.active">\n\t\t\t\t\t\t\t<div class="aicoder-workbench__message-meta">\n\t\t\t\t\t\t\t\t<strong>{{ log.label }}</strong>\n\t\t\t\t\t\t\t\t@if (log.date) {\n\t\t\t\t\t\t\t\t\t<span>{{ log.date }}</span>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<p>{{ log.message }}</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\n\t\t\t\t<label class="aicoder-workbench__reply">\n\t\t\t\t\t<span>Reply to AI</span>\n\t\t\t\t\t<textarea class="aicoder-workbench__textarea" rows="4" [(ngModel)]="chatMessage" [placeholder]="selectedJobIsQuestionOnly ? \'Ask a follow-up question.\' : \'Add more details, ask for another change, or answer a question from AI.\'"></textarea>\n\t\t\t\t</label>\n\t\t\t\t<button type="button" class="aicoder-workbench__primary" (click)="sendChatMessage()" [disabled]="!selectedJobId || !chatMessage.trim() || isSubmitting">\n\t\t\t\t\tSend Reply\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t@else {\n\t\t\t\t<div class="aicoder-workbench__blank">\n\t\t\t\t\t<h3>Select an AI chat</h3>\n\t\t\t\t\t<p>Open a chat from the list or start a new one.</p>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</section>\n\t</div>\n\n\t@if (activeDeploys.length || recentDeploys.length) {\n\t\t<section class="aicoder-workbench__panel aicoder-workbench__panel--updates">\n\t\t\t<div class="aicoder-workbench__panel-head">\n\t\t\t\t<h3>Website Updates</h3>\n\t\t\t\t<span>{{ activeDeploys.length ? \'Running now\' : \'Recent\' }}</span>\n\t\t\t</div>\n\t\t\t<div class="aicoder-workbench__updates">\n\t\t\t\t@for (op of activeDeploys; track op.id || op.jobId) {\n\t\t\t\t\t<div class="aicoder-workbench__update">\n\t\t\t\t\t\t<strong>{{ friendlyDeployStatus(op) }}</strong>\n\t\t\t\t\t\t<span>{{ op.backend_deploy_step || op.aws_build_current_phase || op.message || \'Working on the update\' }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\t@for (op of recentDeploys; track op.id || op.jobId) {\n\t\t\t\t\t<div class="aicoder-workbench__update">\n\t\t\t\t\t\t<strong>{{ friendlyDeployStatus(op) }}</strong>\n\t\t\t\t\t\t<span>{{ op.message || op.backend_deploy_step || \'Update finished\' }}</span>\n\t\t\t\t\t\t<small>{{ formatDate(op.updatedAt || op.createdAt) }}</small>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</section>\n\t}\n</section>\n',styles:['.aicoder-workbench{--aicoder-accent: #2f7d73;--aicoder-accent-dark: #155e57;--aicoder-border: #d8e2ec;--aicoder-ink: #1f2933;--aicoder-muted: #64748b;--aicoder-soft: #f5f8fb;display:flex;flex-direction:column;gap:1rem;color:var(--aicoder-ink)}.aicoder-workbench__header,.aicoder-workbench__intro,.aicoder-workbench__panel-head,.aicoder-workbench__detail-head,.aicoder-workbench__actions{display:flex;align-items:center;justify-content:space-between;gap:1rem}.aicoder-workbench__header p,.aicoder-workbench__eyebrow{margin:0 0 .25rem;color:var(--aicoder-accent-dark);font-size:.75rem;font-weight:800;letter-spacing:0;text-transform:uppercase}.aicoder-workbench__header h2,.aicoder-workbench__intro h3,.aicoder-workbench__panel h3{margin:0;font-weight:800;line-height:1.2}.aicoder-workbench__header h2{font-size:1.35rem}.aicoder-workbench__intro,.aicoder-workbench__runner-console{border:1px solid var(--aicoder-border);border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__runner-head,.aicoder-workbench__runner-kpis,.aicoder-workbench__runner-run-tabs,.aicoder-workbench__runner-tabs{display:flex;flex-wrap:wrap;gap:.75rem}.aicoder-workbench__runner-head{align-items:flex-start;justify-content:space-between;margin-bottom:.85rem}.aicoder-workbench__runner-head h3{margin:0;font-size:1.15rem;line-height:1.25}.aicoder-workbench__runner-head p:not(.aicoder-workbench__eyebrow),.aicoder-workbench__runner-note{color:var(--aicoder-muted);font-size:.86rem;line-height:1.45;margin:.35rem 0 0}.aicoder-workbench__runner-kpis{margin-bottom:.75rem}.aicoder-workbench__runner-kpis>div,.aicoder-workbench__runner-grid>div{border:1px solid #e0e8ef;border-radius:7px;background:#fbfdff;flex:1 1 150px;min-width:0;padding:.7rem}.aicoder-workbench__runner-kpis span,.aicoder-workbench__runner-grid span{display:block;color:var(--aicoder-muted);font-size:.72rem;font-weight:800;text-transform:uppercase}.aicoder-workbench__runner-kpis strong,.aicoder-workbench__runner-grid strong{display:block;color:var(--aicoder-ink);font-size:.9rem;line-height:1.35;margin-top:.2rem;overflow-wrap:anywhere}.aicoder-workbench__runner-run-tabs,.aicoder-workbench__runner-tabs{border-bottom:1px solid #dfe8f0;margin-bottom:.75rem}.aicoder-workbench__runner-run-tabs button,.aicoder-workbench__runner-tabs button{border:0;border-bottom:3px solid transparent;background:transparent;color:var(--aicoder-muted);cursor:pointer;font:inherit;font-size:.82rem;font-weight:800;padding:.45rem .6rem}.aicoder-workbench__runner-run-tabs button.is-active,.aicoder-workbench__runner-tabs button.is-active{border-bottom-color:var(--aicoder-accent);color:var(--aicoder-accent-dark)}.aicoder-workbench__runner-stage-list,.aicoder-workbench__runner-qa-list{display:grid;gap:.5rem}.aicoder-workbench__runner-stage,.aicoder-workbench__runner-qa-list>div{display:grid;grid-template-columns:minmax(150px,.4fr) minmax(0,1fr);gap:.65rem;border:1px solid #e2e8f0;border-radius:7px;background:#fbfdff;padding:.7rem}.aicoder-workbench__runner-stage div,.aicoder-workbench__runner-qa-list>div{min-width:0}.aicoder-workbench__runner-stage strong,.aicoder-workbench__runner-qa-list strong{display:block;font-size:.86rem;line-height:1.3}.aicoder-workbench__runner-stage span,.aicoder-workbench__runner-qa-list span{display:inline-flex;border-radius:999px;background:#eef2f7;color:var(--aicoder-muted);font-size:.7rem;font-weight:800;margin-top:.25rem;padding:.22rem .5rem;text-transform:uppercase}.aicoder-workbench__runner-stage p,.aicoder-workbench__runner-qa-list small{color:var(--aicoder-muted);font-size:.82rem;line-height:1.4;margin:0;overflow-wrap:anywhere}.aicoder-workbench__runner-stage.is-done span,.aicoder-workbench__runner-qa-list span{background:#e7f4f0;color:var(--aicoder-accent-dark)}.aicoder-workbench__runner-stage.is-active span{background:#eef6ff;color:#24547d}.aicoder-workbench__runner-stage.is-blocked span{background:#fff1f2;color:#991b1b}.aicoder-workbench__runner-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem}.aicoder-workbench__intro h3{font-size:1.25rem}.aicoder-workbench__intro p:not(.aicoder-workbench__eyebrow),.aicoder-workbench__blank p,.aicoder-workbench__empty{margin:.35rem 0 0;color:var(--aicoder-muted);line-height:1.45}.aicoder-workbench__layout{display:grid;grid-template-columns:minmax(260px,.75fr) minmax(320px,1.35fr);grid-template-areas:"compose detail" "list detail";gap:1rem;align-items:start}.aicoder-workbench__panel{min-width:0;border:1px solid var(--aicoder-border);border-radius:8px;background:#fff;padding:1rem}.aicoder-workbench__panel--compose{grid-area:compose}.aicoder-workbench__panel--list{grid-area:list}.aicoder-workbench__panel--detail{grid-area:detail}.aicoder-workbench__panel--updates{display:grid;gap:.75rem}.aicoder-workbench__panel-head span,.aicoder-workbench__status{display:inline-flex;align-items:center;border-radius:999px;background:#e7f4f0;color:var(--aicoder-accent-dark);font-size:.78rem;font-weight:800;padding:.25rem .6rem}.aicoder-workbench__next-step,.aicoder-workbench__thinking{display:flex;align-items:center;gap:.65rem;border:1px solid #dbe7ef;border-radius:8px;background:#f7fafc;color:var(--aicoder-muted);margin-top:1rem;padding:.85rem}.aicoder-workbench__next-step p,.aicoder-workbench__thinking p{margin:0;line-height:1.4}.aicoder-workbench__spinner{display:inline-block;width:1rem;height:1rem;border:2px solid #bfd2df;border-top-color:var(--aicoder-accent);border-radius:999px;flex:0 0 auto;animation:aicoder-workbench-spin .85s linear infinite}.aicoder-workbench__input,.aicoder-workbench__textarea{width:100%;border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:var(--aicoder-ink);padding:.7rem .8rem}.aicoder-workbench__textarea{resize:vertical}.aicoder-workbench__input,.aicoder-workbench__textarea{margin:.75rem 0}.aicoder-workbench__ghost,.aicoder-workbench__primary,.aicoder-workbench__danger,.aicoder-workbench__actions button{border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:var(--aicoder-ink);cursor:pointer;font:inherit;font-weight:800;min-height:40px;padding:.6rem .85rem;text-decoration:none}.aicoder-workbench__primary{border-color:var(--aicoder-accent);background:var(--aicoder-accent);color:#fff}.aicoder-workbench__danger{border-color:#fecaca;color:#991b1b}.aicoder-workbench button:disabled{cursor:not-allowed;opacity:.55}.aicoder-workbench__chat-list,.aicoder-workbench__history,.aicoder-workbench__updates{display:flex;flex-direction:column;gap:.5rem}.aicoder-workbench__chat-list,.aicoder-workbench__history{max-height:520px;overflow:auto}.aicoder-workbench__chat,.aicoder-workbench__message,.aicoder-workbench__update{display:flex;flex-direction:column;align-items:flex-start;gap:.25rem;width:100%;border:1px solid #e2e8f0;border-radius:7px;background:var(--aicoder-soft);padding:.8rem;text-align:left}.aicoder-workbench__message{background:#fff}.aicoder-workbench__message--user{border-color:#c9d8e6;background:#f8fafc}.aicoder-workbench__message--assistant{border-color:#b7ded3;background:#f4fbf9}.aicoder-workbench__message--system{background:#fbfdff}.aicoder-workbench__message.is-active{border-color:var(--aicoder-accent)}.aicoder-workbench__message-meta{display:flex;align-items:center;justify-content:space-between;gap:.75rem;width:100%;color:var(--aicoder-muted);font-size:.8rem;line-height:1.35}.aicoder-workbench__message-meta strong{color:var(--aicoder-ink);font-size:.82rem}.aicoder-workbench__chat{cursor:pointer}.aicoder-workbench__chat.is-active{border-color:var(--aicoder-accent);background:#ecfdf5}.aicoder-workbench__chat span,.aicoder-workbench__chat small,.aicoder-workbench__message span,.aicoder-workbench__update span,.aicoder-workbench__update small{color:var(--aicoder-muted);font-size:.82rem;line-height:1.35}.aicoder-workbench__message p{margin:0;white-space:pre-wrap;overflow-wrap:anywhere}.aicoder-workbench__history{min-height:260px;margin:.75rem 0}.aicoder-workbench__qa{border:1px solid #dbe7ef;border-radius:8px;background:#fbfdff;margin-top:1rem;padding:.85rem}.aicoder-workbench__run-evidence{border:1px solid #d7e3ee;border-radius:8px;background:#fbfcfe;margin-top:1rem;padding:.85rem}.aicoder-workbench__run-evidence-head{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}.aicoder-workbench__run-evidence-head h4{margin:0;font-size:1rem;line-height:1.25;text-transform:capitalize}.aicoder-workbench__run-evidence-head span{border-radius:999px;background:#eef6ff;color:#24547d;font-size:.78rem;font-weight:800;padding:.25rem .6rem;text-transform:capitalize}.aicoder-workbench__run-evidence-text,.aicoder-workbench__run-evidence-action{color:var(--aicoder-muted);font-size:.84rem;line-height:1.45;margin:.65rem 0 0}.aicoder-workbench__run-evidence-action{color:var(--aicoder-ink);font-weight:700}.aicoder-workbench__retry-lane{display:flex;align-items:center;flex-wrap:wrap;gap:.45rem;margin-top:.7rem}.aicoder-workbench__retry-lane span,.aicoder-workbench__retry-lane strong,.aicoder-workbench__retry-lane small{border-radius:999px;font-size:.72rem;line-height:1;padding:.38rem .5rem;text-transform:uppercase}.aicoder-workbench__retry-lane span{background:#eef6ff;color:#24547d}.aicoder-workbench__retry-lane strong{background:#f1f5f9;color:var(--aicoder-ink)}.aicoder-workbench__retry-lane small{background:#fff7ed;color:#9a3412;font-weight:700}.aicoder-workbench__gate-list{display:grid;gap:.5rem;margin-top:.75rem}.aicoder-workbench__gate{display:grid;grid-template-columns:minmax(120px,.8fr) auto;gap:.25rem .65rem;border:1px solid #e2e8f0;border-radius:7px;background:#fff;padding:.65rem}.aicoder-workbench__gate strong{font-size:.82rem;line-height:1.3}.aicoder-workbench__gate span{border-radius:999px;background:#f1f5f9;color:var(--aicoder-muted);font-size:.72rem;font-weight:800;line-height:1.2;padding:.2rem .5rem;text-transform:uppercase}.aicoder-workbench__gate small{grid-column:1/-1;color:var(--aicoder-muted);font-size:.78rem;line-height:1.35;overflow-wrap:anywhere}.aicoder-workbench__gate.is-pass span{background:#e7f4f0;color:var(--aicoder-accent-dark)}.aicoder-workbench__gate.is-fail span{background:#fff1f2;color:#991b1b}.aicoder-workbench__qa-head{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:.75rem}.aicoder-workbench__qa-head h4{margin:0;font-size:1rem;line-height:1.25}.aicoder-workbench__qa-head span,.aicoder-workbench__qa-empty{color:var(--aicoder-muted);font-size:.82rem;line-height:1.4;margin:0}.aicoder-workbench__qa-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem}.aicoder-workbench__qa-card{display:flex;flex-direction:column;gap:.45rem;border:1px solid #dbe7ef;border-radius:7px;background:#fff;color:var(--aicoder-ink);overflow:hidden;text-decoration:none}.aicoder-workbench__qa-card img{display:block;width:100%;aspect-ratio:16/10;background:#eef3f7;object-fit:cover;object-position:top center}.aicoder-workbench__qa-card span{color:var(--aicoder-ink);font-size:.82rem;font-weight:800;line-height:1.3;padding:0 .65rem .65rem}.aicoder-workbench__actions{flex-wrap:wrap;justify-content:flex-start;margin-top:1rem}.aicoder-workbench__reply{display:block;margin-top:1rem}.aicoder-workbench__reply span{display:block;color:var(--aicoder-muted);font-size:.78rem;font-weight:800;text-transform:uppercase}.aicoder-workbench__blank{display:grid;min-height:280px;place-content:center;text-align:center}.aicoder-workbench__notice{border-radius:6px;padding:.75rem .9rem;font-weight:800}.aicoder-workbench__notice--error{border:1px solid #fecaca;background:#fff1f2;color:#991b1b}.aicoder-workbench__notice--success{border:1px solid #bbf7d0;background:#f0fdf4;color:#166534}@keyframes aicoder-workbench-spin{to{transform:rotate(360deg)}}.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:minmax(240px,.7fr) minmax(320px,1.3fr)}@media(max-width:980px){.aicoder-workbench__layout,.aicoder-workbench--compact .aicoder-workbench__layout{grid-template-columns:1fr;grid-template-areas:"compose" "list" "detail"}.aicoder-workbench__header,.aicoder-workbench__intro,.aicoder-workbench__detail-head{align-items:stretch;flex-direction:column}.aicoder-workbench__actions button,.aicoder-workbench__primary,.aicoder-workbench__ghost,.aicoder-workbench__danger{width:100%}}\n']}]}],ctorParameters:()=>[{type:AICoderWorkbenchApiService}],propDecorators:{appId:[{type:Input}],appName:[{type:Input}],apiBase:[{type:Input}],appToken:[{type:Input}],defaultTab:[{type:Input}],compact:[{type:Input}],showHeader:[{type:Input}],autoRefresh:[{type:Input}]}});class AICoderDashboardModule{static"ɵfac"=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,deps:[],target:i0.ɵɵFactoryTarget.NgModule});static"ɵmod"=i0.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,declarations:[AICoderDashboardComponent,AICoderWorkbenchComponent],imports:[CommonModule,FormsModule,HttpClientModule],exports:[AICoderDashboardComponent,AICoderWorkbenchComponent]});static"ɵinj"=i0.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,imports:[CommonModule,FormsModule,HttpClientModule]})}i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"21.1.2",ngImport:i0,type:AICoderDashboardModule,decorators:[{type:NgModule,args:[{imports:[CommonModule,FormsModule,HttpClientModule],declarations:[AICoderDashboardComponent,AICoderWorkbenchComponent],exports:[AICoderDashboardComponent,AICoderWorkbenchComponent]}]}]});export{AICoderDashboardComponent,AICoderDashboardModule,AICoderDashboardService,AICoderWorkbenchApiService,AICoderWorkbenchComponent};
package/package.json CHANGED
@@ -1,11 +1,6 @@
1
1
  {
2
2
  "name": "@resolveio/client-lib-aicoder-dashboard",
3
- "version": "21.0.11",
4
- "peerDependencies": {
5
- "@angular/common": "^21.0.0",
6
- "@angular/core": "^21.0.0",
7
- "@resolveio/client-lib-core": "^21.0.0"
8
- },
3
+ "version": "21.0.14",
9
4
  "dependencies": {
10
5
  "tslib": "^2.8.1"
11
6
  },
@@ -63,6 +63,10 @@ interface AICoderWorkbenchJob {
63
63
  stage?: string;
64
64
  phase?: string;
65
65
  project?: string;
66
+ currentCycle?: number;
67
+ lastRerunReason?: string;
68
+ generation_mode?: string;
69
+ wow_score?: number;
66
70
  createdAt?: string;
67
71
  updatedAt?: string;
68
72
  pr_url?: string;
@@ -73,6 +77,9 @@ interface AICoderWorkbenchJob {
73
77
  pullRequestStatus?: string;
74
78
  branch?: string;
75
79
  github_branch?: string;
80
+ qaEvidence?: AICoderWorkbenchQaEvidence[] | AICoderWorkbenchQaRunEvidence;
81
+ qa_evidence?: AICoderWorkbenchQaEvidence[] | AICoderWorkbenchQaRunEvidence;
82
+ qaRunEvidence?: AICoderWorkbenchQaRunEvidence;
76
83
  summary?: string;
77
84
  responseSummary?: string;
78
85
  last_message?: string;
@@ -82,12 +89,61 @@ interface AICoderWorkbenchJob {
82
89
  questionOnly?: boolean;
83
90
  [key: string]: any;
84
91
  };
92
+ aiCoderRunnerV6?: {
93
+ aiCoderRunnerVersion?: string;
94
+ aiCoderV6SupervisorState?: Record<string, any>;
95
+ aiCoderV6LaneMemory?: Record<string, any>;
96
+ aiCoderV6WorkflowMemory?: Record<string, any>;
97
+ aiCoderV6StepHistory?: Array<Record<string, any>>;
98
+ aiCoderV6Budget?: Record<string, any>;
99
+ aiCoderV6RunnerIncidents?: Array<Record<string, any>>;
100
+ };
101
+ publishLock?: {
102
+ active?: boolean;
103
+ reason?: string;
104
+ gate?: string;
105
+ recordedAt?: string;
106
+ };
85
107
  artifacts?: {
86
108
  modifiedFiles?: Record<string, string>;
87
109
  diffs?: Record<string, string>;
88
110
  agentNotes?: Record<string, string>;
89
111
  };
90
112
  }
113
+ interface AICoderWorkbenchQaRunGate {
114
+ key?: string;
115
+ label?: string;
116
+ status?: string;
117
+ reason?: string;
118
+ evidenceRefs?: string[];
119
+ recordedAt?: string;
120
+ }
121
+ interface AICoderWorkbenchQaRunEvidence {
122
+ qaRunOutcome?: string;
123
+ aiRunOutcome?: string;
124
+ aiRunOutcomeReason?: string;
125
+ aiRunNextAction?: string;
126
+ aiRunGates?: AICoderWorkbenchQaRunGate[];
127
+ aiRunWarnings?: string[];
128
+ aiRunRetryAction?: string;
129
+ aiRunFailureClass?: string;
130
+ aiRunRetryReason?: string;
131
+ aiRunRepeatedFailure?: boolean;
132
+ aiRunRetryExpectedValue?: number;
133
+ methodSync?: {
134
+ status?: string;
135
+ [key: string]: any;
136
+ };
137
+ routeUniqueness?: {
138
+ status?: string;
139
+ [key: string]: any;
140
+ };
141
+ browserQa?: {
142
+ status?: string;
143
+ [key: string]: any;
144
+ };
145
+ updatedAt?: string;
146
+ }
91
147
  interface AICoderWorkbenchQaEvidence {
92
148
  id: string;
93
149
  title: string;
@@ -209,6 +265,13 @@ interface AICoderWorkbenchTimelineItem {
209
265
  date?: string;
210
266
  active?: boolean;
211
267
  }
268
+ type AICoderRunnerConsoleTab = 'runs' | 'journey' | 'qa' | 'release' | 'cost';
269
+ interface AICoderRunnerStage {
270
+ key: string;
271
+ label: string;
272
+ status: string;
273
+ summary: string;
274
+ }
212
275
  declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy {
213
276
  private readonly api;
214
277
  appId: string;
@@ -220,6 +283,7 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
220
283
  showHeader: boolean;
221
284
  autoRefresh: boolean;
222
285
  activeTab: AICoderWorkbenchTab;
286
+ runnerConsoleTab: AICoderRunnerConsoleTab;
223
287
  jobs: AICoderWorkbenchJob[];
224
288
  selectedJob: AICoderWorkbenchJob | null;
225
289
  jobLogs: AICoderWorkbenchTimelineItem[];
@@ -248,6 +312,9 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
248
312
  ngOnChanges(changes: SimpleChanges): void;
249
313
  ngOnDestroy(): void;
250
314
  setTab(tab: AICoderWorkbenchTab): void;
315
+ setRunnerConsoleTab(tab: AICoderRunnerConsoleTab): void;
316
+ selectRunnerRun(job: AICoderWorkbenchJob): void;
317
+ runRunnerPrimaryAction(): void;
251
318
  refresh(): void;
252
319
  selectJob(job: AICoderWorkbenchJob): void;
253
320
  submitRequest(): void;
@@ -272,14 +339,38 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
272
339
  get activeDeploys(): AICoderWorkbenchDeployOperation[];
273
340
  get recentDeploys(): AICoderWorkbenchDeployOperation[];
274
341
  get visibleJobs(): AICoderWorkbenchJob[];
342
+ get runnerConsoleTabs(): Array<{
343
+ id: AICoderRunnerConsoleTab;
344
+ label: string;
345
+ }>;
346
+ get runnerRuns(): AICoderWorkbenchJob[];
347
+ get selectedJobRunNumber(): number;
348
+ get selectedJobTriggerReason(): string;
349
+ get selectedJobOutcomeLabel(): string;
350
+ get selectedJobBlocker(): string;
351
+ get selectedJobPrimaryActionLabel(): string;
352
+ get selectedJobPrimaryActionReason(): string;
353
+ get selectedJobRunnerSupervisor(): Record<string, any>;
354
+ get selectedJobWorkflowMemory(): Record<string, any>;
355
+ get selectedJobBudget(): Record<string, any>;
356
+ get selectedJobStepHistory(): Array<Record<string, any>>;
357
+ get selectedJobWorkflowQaRows(): Array<Record<string, any>>;
358
+ get selectedJobCompletedWorkflowSteps(): string[];
359
+ get selectedJobBusinessProofArtifacts(): string[];
360
+ get selectedJobRunnerStages(): AICoderRunnerStage[];
361
+ get selectedJobEstimatedCostUsd(): number;
275
362
  get hasSelectedJob(): boolean;
276
363
  get selectedJobIsWorking(): boolean;
277
364
  get selectedJobIsQuestionOnly(): boolean;
278
365
  get selectedJobHasChanges(): boolean;
279
366
  get selectedJobHasReview(): boolean;
280
367
  get selectedJobTestSiteUrl(): string;
368
+ get selectedJobQaRunEvidence(): AICoderWorkbenchQaRunEvidence | null;
369
+ get selectedJobQaRunGates(): AICoderWorkbenchQaRunGate[];
370
+ get selectedJobHasQaRunEvidence(): boolean;
281
371
  get canSendSelectedJobToTestSite(): boolean;
282
372
  get canApproveSelectedJobLive(): boolean;
373
+ get selectedJobIsPublished(): boolean;
283
374
  get selectedJobNextStepMessage(): string;
284
375
  isActiveJob(job: AICoderWorkbenchJob | null): boolean;
285
376
  resolvePrUrl(job: AICoderWorkbenchJob | null): string;
@@ -307,6 +398,10 @@ declare class AICoderWorkbenchComponent implements OnInit, OnChanges, OnDestroy
307
398
  private runJobAction;
308
399
  private mergeJob;
309
400
  private normalizeJob;
401
+ private extractJobQaEvidence;
402
+ private extractJobQaRunEvidence;
403
+ private normalizeQaEvidenceList;
404
+ private isPublishedJob;
310
405
  private normalizeTab;
311
406
  private toTitle;
312
407
  private startAutoRefresh;
@@ -323,4 +418,4 @@ declare class AICoderDashboardModule {
323
418
  }
324
419
 
325
420
  export { AICoderDashboardComponent, AICoderDashboardModule, AICoderDashboardService, AICoderWorkbenchApiService, AICoderWorkbenchComponent };
326
- export type { AICoderDashboardSummary, AICoderDashboardTab, AICoderTier, AICoderTierOption, AICoderWorkbenchConversationEntry, AICoderWorkbenchDeployJob, AICoderWorkbenchDeployOperation, AICoderWorkbenchGitStatus, AICoderWorkbenchJob, AICoderWorkbenchLog, AICoderWorkbenchQaEvidence, AICoderWorkbenchTab };
421
+ export type { AICoderDashboardSummary, AICoderDashboardTab, AICoderTier, AICoderTierOption, AICoderWorkbenchConversationEntry, AICoderWorkbenchDeployJob, AICoderWorkbenchDeployOperation, AICoderWorkbenchGitStatus, AICoderWorkbenchJob, AICoderWorkbenchLog, AICoderWorkbenchQaEvidence, AICoderWorkbenchQaRunEvidence, AICoderWorkbenchQaRunGate, AICoderWorkbenchTab };