@json-to-office/jto 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/cli.js +60 -19
  2. package/dist/cli.js.map +1 -1
  3. package/dist/client/assets/HomePage-fDrbL-5G.js +106 -0
  4. package/dist/client/assets/HomePage-fDrbL-5G.js.map +1 -0
  5. package/dist/client/assets/{JsonEditorPage-4LrF2fB9.js → JsonEditorPage-CRY9589Y.js} +3 -3
  6. package/dist/client/assets/{JsonEditorPage-4LrF2fB9.js.map → JsonEditorPage-CRY9589Y.js.map} +1 -1
  7. package/dist/client/assets/MonacoPluginProvider-n1_V7jsc.js +3 -0
  8. package/dist/client/assets/MonacoPluginProvider-n1_V7jsc.js.map +1 -0
  9. package/dist/client/assets/{editor-D-REaAU9.js → editor-Y9dek093.js} +2 -2
  10. package/dist/client/assets/{editor-D-REaAU9.js.map → editor-Y9dek093.js.map} +1 -1
  11. package/dist/client/assets/{editor-monaco-json-BgziSLMV.js → editor-monaco-json-oBxvTIvi.js} +2 -2
  12. package/dist/client/assets/{editor-monaco-json-BgziSLMV.js.map → editor-monaco-json-oBxvTIvi.js.map} +1 -1
  13. package/dist/client/assets/{editor-refs-store-vzLK2-Zo.js → editor-refs-store-BMwl0ejj.js} +2 -2
  14. package/dist/client/assets/{editor-refs-store-vzLK2-Zo.js.map → editor-refs-store-BMwl0ejj.js.map} +1 -1
  15. package/dist/client/assets/{index-B7bDnXoC.js → index-B4R-AAtI.js} +4 -4
  16. package/dist/client/assets/index-B4R-AAtI.js.map +1 -0
  17. package/dist/client/assets/preview-zCYkbgdR.js +2 -0
  18. package/dist/client/assets/preview-zCYkbgdR.js.map +1 -0
  19. package/dist/client/assets/{useQualityAnalysis-CowAaoA9.js → useQualityAnalysis-BFG1bJDp.js} +2 -2
  20. package/dist/client/assets/{useQualityAnalysis-CowAaoA9.js.map → useQualityAnalysis-BFG1bJDp.js.map} +1 -1
  21. package/dist/client/index.html +1 -1
  22. package/package.json +5 -5
  23. package/dist/client/assets/HomePage-CeUQIXch.js +0 -106
  24. package/dist/client/assets/HomePage-CeUQIXch.js.map +0 -1
  25. package/dist/client/assets/MonacoPluginProvider-Bx_V_FaE.js +0 -3
  26. package/dist/client/assets/MonacoPluginProvider-Bx_V_FaE.js.map +0 -1
  27. package/dist/client/assets/index-B7bDnXoC.js.map +0 -1
  28. package/dist/client/assets/preview-BGqIip90.js +0 -2
  29. package/dist/client/assets/preview-BGqIip90.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import{r as p}from"./react-vendor-BmQ2Ko_D.js";import{p as z,F as S,A as J}from"./index-B7bDnXoC.js";import{b as Y,f as G,d as T}from"./MonacoPluginProvider-Bx_V_FaE.js";import{l as K}from"./HomePage-CeUQIXch.js";const V=["error","warning","info"],N={error:0,warning:1,info:2},O=3;function b(e){return typeof e=="string"&&e in N?N[e]:O}const W=["deterministic","measured","estimated","rendered","evaluative"],X=["add","replace","remove","move","copy","test"],Z="other";function m(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function H(e){return m(e)}function y(e){return typeof e=="string"?e:void 0}function A(e){return typeof e=="string"&&V.includes(e)?e:void 0}function B(e){return typeof e=="string"&&W.includes(e)?e:void 0}function ee(e){if(!Array.isArray(e))return;const t=e.filter(r=>typeof r=="string");return t.length>0?t:void 0}function te(e){return m(e)?e:void 0}function re(e){if(!Array.isArray(e))return;const t=[];for(const r of e){if(!m(r))continue;const n=r.op,o=r.path;if(typeof n!="string"||typeof o!="string"||!X.includes(n))continue;const s={op:n,path:o};"value"in r&&(s.value=r.value);const i=y(r.from);i!==void 0&&(s.from=i),t.push(s)}return t.length>0?t:void 0}function ne(e,t){if(!t)return e;const r=`[${t}] `;return e.startsWith(r)?e.slice(r.length):e}function oe(e,t,r){return`${e||"quality"}:${t}:${r}`}function Q(e,t){const r=y(e.code)??"",n=y(e.message)??"";if(!r&&!n)return;const o=y(e.path)??"",s={id:oe(r,o,t),code:r,message:ne(n,r)||r,path:o,severity:e.severity},i=y(e.ruleId);i!==void 0&&(s.ruleId=i);const l=y(e.category);l!==void 0&&(s.category=l);const a=B(e.certainty);a!==void 0&&(s.certainty=a),typeof e.blocking=="boolean"&&(s.blocking=e.blocking);const u=y(e.suggestion);u!==void 0&&(s.suggestion=u);const g=ee(e.relatedPaths);g!==void 0&&(s.relatedPaths=g);const I=te(e.evidence);I!==void 0&&(s.evidence=I);const c=re(e.fixes);return c!==void 0&&(s.fixes=c),s}function Ae(e){const t=[],r=[];if(!Array.isArray(e))return{findings:t,others:r};for(const n of e){if(!H(n))continue;if(n.component!=="quality"){r.push(n);continue}const o=m(n.context)?n.context:{},s=A(o.originalSeverity)??A(n.severity)??"warning",i=Q({code:o.code,message:n.message,path:o.path,severity:s,ruleId:o.ruleId,category:o.category,certainty:o.certainty,blocking:o.blocking,suggestion:o.suggestion,relatedPaths:o.relatedPaths,evidence:o.evidence,fixes:o.fixes},t.length);i&&t.push(i)}return{findings:t,others:r}}function M(e){if(!m(e))return[];const t=e.diagnostics;if(!Array.isArray(t))return[];const r=[];for(const n of t){if(!m(n))continue;const o=Q({code:n.code,message:n.message,path:n.path,severity:A(n.severity)??"warning",ruleId:n.ruleId,category:n.category,certainty:n.certainty,blocking:n.blocking,suggestion:n.suggestion,relatedPaths:n.relatedPaths,evidence:n.evidence,fixes:n.fixes},r.length);o&&r.push(o)}return r}function _(e){const t={error:0,warning:0,info:0};if(!Array.isArray(e))return t;for(const r of e){const n=A(r==null?void 0:r.severity);n&&(t[n]+=1)}return t}function ie(e,t){const r=b(e==null?void 0:e.severity),n=b(t==null?void 0:t.severity);if(r!==n)return r-n;const o=(e==null?void 0:e.path)??"",s=(t==null?void 0:t.path)??"";if(o!==s)return o<s?-1:1;const i=(e==null?void 0:e.code)??"",l=(t==null?void 0:t.code)??"";return i===l?0:i<l?-1:1}function xe(e){const t=new Map;if(!Array.isArray(e))return[];for(const n of e){if(!n)continue;const o=n.category||Z,s=t.get(o);s?s.push(n):t.set(o,[n])}const r=n=>n.reduce((o,s)=>{const i=b(s.severity);return i<o?i:o},O);return Array.from(t.entries()).map(([n,o])=>({category:n,findings:[...o].sort(ie)})).sort((n,o)=>{const s=r(n.findings)-r(o.findings);return s!==0?s:n.category===o.category?0:n.category<o.category?-1:1})}function Ee(e,t){if(!Array.isArray(e))return[];const r=A(t)?b(t):O;return e.filter(n=>b(n==null?void 0:n.severity)<=r)}function Se(e){const t=B(e);if(t)return t.charAt(0).toUpperCase()+t.slice(1)}const Ie=`{
1
+ import{r as p}from"./react-vendor-BmQ2Ko_D.js";import{p as z,F as S,A as J}from"./index-B4R-AAtI.js";import{b as Y,h as G,d as T}from"./MonacoPluginProvider-n1_V7jsc.js";import{l as K}from"./HomePage-fDrbL-5G.js";const V=["error","warning","info"],N={error:0,warning:1,info:2},O=3;function b(e){return typeof e=="string"&&e in N?N[e]:O}const W=["deterministic","measured","estimated","rendered","evaluative"],X=["add","replace","remove","move","copy","test"],Z="other";function m(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function H(e){return m(e)}function y(e){return typeof e=="string"?e:void 0}function A(e){return typeof e=="string"&&V.includes(e)?e:void 0}function B(e){return typeof e=="string"&&W.includes(e)?e:void 0}function ee(e){if(!Array.isArray(e))return;const t=e.filter(r=>typeof r=="string");return t.length>0?t:void 0}function te(e){return m(e)?e:void 0}function re(e){if(!Array.isArray(e))return;const t=[];for(const r of e){if(!m(r))continue;const n=r.op,o=r.path;if(typeof n!="string"||typeof o!="string"||!X.includes(n))continue;const s={op:n,path:o};"value"in r&&(s.value=r.value);const i=y(r.from);i!==void 0&&(s.from=i),t.push(s)}return t.length>0?t:void 0}function ne(e,t){if(!t)return e;const r=`[${t}] `;return e.startsWith(r)?e.slice(r.length):e}function oe(e,t,r){return`${e||"quality"}:${t}:${r}`}function Q(e,t){const r=y(e.code)??"",n=y(e.message)??"";if(!r&&!n)return;const o=y(e.path)??"",s={id:oe(r,o,t),code:r,message:ne(n,r)||r,path:o,severity:e.severity},i=y(e.ruleId);i!==void 0&&(s.ruleId=i);const l=y(e.category);l!==void 0&&(s.category=l);const a=B(e.certainty);a!==void 0&&(s.certainty=a),typeof e.blocking=="boolean"&&(s.blocking=e.blocking);const u=y(e.suggestion);u!==void 0&&(s.suggestion=u);const g=ee(e.relatedPaths);g!==void 0&&(s.relatedPaths=g);const I=te(e.evidence);I!==void 0&&(s.evidence=I);const c=re(e.fixes);return c!==void 0&&(s.fixes=c),s}function Ae(e){const t=[],r=[];if(!Array.isArray(e))return{findings:t,others:r};for(const n of e){if(!H(n))continue;if(n.component!=="quality"){r.push(n);continue}const o=m(n.context)?n.context:{},s=A(o.originalSeverity)??A(n.severity)??"warning",i=Q({code:o.code,message:n.message,path:o.path,severity:s,ruleId:o.ruleId,category:o.category,certainty:o.certainty,blocking:o.blocking,suggestion:o.suggestion,relatedPaths:o.relatedPaths,evidence:o.evidence,fixes:o.fixes},t.length);i&&t.push(i)}return{findings:t,others:r}}function M(e){if(!m(e))return[];const t=e.diagnostics;if(!Array.isArray(t))return[];const r=[];for(const n of t){if(!m(n))continue;const o=Q({code:n.code,message:n.message,path:n.path,severity:A(n.severity)??"warning",ruleId:n.ruleId,category:n.category,certainty:n.certainty,blocking:n.blocking,suggestion:n.suggestion,relatedPaths:n.relatedPaths,evidence:n.evidence,fixes:n.fixes},r.length);o&&r.push(o)}return r}function _(e){const t={error:0,warning:0,info:0};if(!Array.isArray(e))return t;for(const r of e){const n=A(r==null?void 0:r.severity);n&&(t[n]+=1)}return t}function ie(e,t){const r=b(e==null?void 0:e.severity),n=b(t==null?void 0:t.severity);if(r!==n)return r-n;const o=(e==null?void 0:e.path)??"",s=(t==null?void 0:t.path)??"";if(o!==s)return o<s?-1:1;const i=(e==null?void 0:e.code)??"",l=(t==null?void 0:t.code)??"";return i===l?0:i<l?-1:1}function xe(e){const t=new Map;if(!Array.isArray(e))return[];for(const n of e){if(!n)continue;const o=n.category||Z,s=t.get(o);s?s.push(n):t.set(o,[n])}const r=n=>n.reduce((o,s)=>{const i=b(s.severity);return i<o?i:o},O);return Array.from(t.entries()).map(([n,o])=>({category:n,findings:[...o].sort(ie)})).sort((n,o)=>{const s=r(n.findings)-r(o.findings);return s!==0?s:n.category===o.category?0:n.category<o.category?-1:1})}function Ee(e,t){if(!Array.isArray(e))return[];const r=A(t)?b(t):O;return e.filter(n=>b(n==null?void 0:n.severity)<=r)}function Se(e){const t=B(e);if(t)return t.charAt(0).toUpperCase()+t.slice(1)}const Ie=`{
2
2
  "rules": {},
3
3
  "suppressions": []
4
4
  }
5
5
  `,se=new Set(["error","warning","info"]);function k(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function ae(e){const t=(e??"").trim();if(!t)return{ok:!0,policy:void 0};let r;try{r=JSON.parse(t)}catch(a){return{ok:!1,error:`Not valid JSON: ${a.message}`}}if(!k(r))return{ok:!1,error:"A policy must be a JSON object."};if("gate"in r)return{ok:!1,error:'Remove "gate" — it is set by the Gate control above.'};if("profile"in r)return{ok:!1,error:'Remove "profile" — it is set by the Profile control above.'};const n=new Set(["rules","suppressions","maxDiagnostics","onRuleError"]),o=Object.keys(r).find(a=>!n.has(a));if(o!==void 0)return{ok:!1,error:`Unknown key "${o}". A policy takes: ${[...n].join(", ")}.`};const s=new Set(z().map(a=>a.id)),i={};if(r.rules!==void 0){if(!k(r.rules))return{ok:!1,error:'"rules" must be an object keyed by rule id.'};for(const[a,u]of Object.entries(r.rules)){if(!s.has(a))return{ok:!1,error:`Unknown rule "${a}". This format ships: ${[...s].join(", ")}.`};if(!k(u))return{ok:!1,error:`Rule "${a}" must be an object, e.g. { "severity": "warning" }.`};if(u.severity!==void 0&&!se.has(u.severity))return{ok:!1,error:`Rule "${a}" has an invalid severity. Use "error", "warning" or "info".`};if(u.enabled!==void 0&&typeof u.enabled!="boolean")return{ok:!1,error:`Rule "${a}": "enabled" must be true or false.`};if(u.parameters!==void 0&&!k(u.parameters))return{ok:!1,error:`Rule "${a}": "parameters" must be an object.`}}i.rules=r.rules}if(r.suppressions!==void 0){if(!Array.isArray(r.suppressions))return{ok:!1,error:'"suppressions" must be an array.'};for(const[a,u]of r.suppressions.entries()){if(!k(u))return{ok:!1,error:`Suppression ${a} must be an object.`};if(typeof u.reason!="string"||!u.reason.trim())return{ok:!1,error:`Suppression ${a} needs a "reason" saying why the finding is acceptable.`};if(u.pathMatch!==void 0&&u.pathMatch!=="exact"&&u.pathMatch!=="subtree")return{ok:!1,error:`Suppression ${a}: "pathMatch" must be "exact" or "subtree".`}}i.suppressions=r.suppressions}if(r.maxDiagnostics!==void 0){if(!Number.isInteger(r.maxDiagnostics)||r.maxDiagnostics<0)return{ok:!1,error:'"maxDiagnostics" must be a whole number, zero or more.'};i.maxDiagnostics=r.maxDiagnostics}if(r.onRuleError!==void 0){if(r.onRuleError!=="continue"&&r.onRuleError!=="throw")return{ok:!1,error:'"onRuleError" must be "continue" or "throw".'};i.onRuleError=r.onRuleError}return{ok:!0,policy:(i.rules===void 0||Object.keys(i.rules).length===0)&&(i.suppressions===void 0||i.suppressions.length===0)&&i.maxDiagnostics===void 0&&i.onRuleError===void 0?void 0:i}}const ce={docx:[{id:"executive-report",label:"Executive report",description:"Short decision document with strict outline continuity"},{id:"technical-report",label:"Technical report",description:"Portable professional report defaults"},{id:"legal-appendix",label:"Legal appendix",description:"Dense appendix: preserve integrity without editorial taste"}],pptx:[{id:"executive-presentation",label:"Executive presentation",description:"Decision deck optimized for scan speed and projection"},{id:"technical-presentation",label:"Technical presentation",description:"Portable professional presentation defaults"}]};function ue(e){const t=e==null?void 0:e[S];return U(t)?t:void 0}function U(e){return e!==void 0&&ce[S].some(t=>t.id===e)}function le(e,t,r){const n={};U(e)&&(n.profile={id:e,formats:[S]});const o=ae(r),s=o.ok?o.policy:void 0,i=t==="error"||t==="warning"||t==="info";return(s||i)&&(n.policy={...s??{},...i&&{gate:t}}),n.profile||n.policy?n:void 0}let L=0;function fe(){return L+=1,L}function de(e,t){return e!=null&&e.seq>t}const pe=1500;async function ye(e){if(K(e.headers.get("content-type")))try{return await e.json()}catch{return}}function ge(e){if(!e||typeof e!="object")return{};const t=e;return{...typeof t.profileId=="string"?{profileId:t.profileId}:{},...typeof t.blocked=="boolean"?{blocked:t.blocked}:{},...typeof t.truncated=="boolean"?{truncated:t.truncated}:{}}}function ve(e,t){return t!=null&&t.error?t.error:`Quality analysis failed (${e.status}${e.statusText?` ${e.statusText}`:""}).`}function we(){const e=Y(c=>c.setOutput),t=p.useContext(G),r=T(c=>c.qualityProfileIds),n=ue(r),o=T(c=>c.qualityGate),s=T(c=>{var f;return(f=c.qualityPolicies)==null?void 0:f[S]}),i=p.useRef(null),l=p.useRef(null),a=p.useCallback(()=>{var c;i.current!==null&&(clearTimeout(i.current),i.current=null),(c=l.current)==null||c.abort(),l.current=null},[]);p.useEffect(()=>a,[a]);const u=p.useCallback((c,f)=>{de(t.getState().quality,c)||e({quality:f})},[t,e]),g=p.useCallback(async(c,f)=>{var D,$;let v;try{v=JSON.parse(f)}catch{return}(D=l.current)==null||D.abort();const x=new AbortController;l.current=x;const h=fe(),P=le(n,o,s);try{const w=await fetch(J.validate,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({jsonDefinition:v,options:P?{quality:P}:{}}),signal:x.signal}),d=await ye(w);if(x.signal.aborted)return;if(!w.ok){const q=ve(w,d);if((d==null?void 0:d.code)==="QUALITY_GATE_FAILED"){const F=M(d.quality);u(h,{findings:F,counts:_(F),documentName:c,seq:h,blocked:!0,source:"validate",analyzedAt:Date.now(),gateError:q});return}const R=t.getState().quality,E=R&&R.documentName===c?R:void 0;u(h,{...E??{findings:[],counts:{error:0,warning:0,info:0},source:"validate"},documentName:c,seq:h,analyzedAt:Date.now(),blocked:(E==null?void 0:E.blocked)??!1,gateError:void 0,analysisError:q});return}const j=($=d==null?void 0:d.data)==null?void 0:$.qualityAnalysis,C=M(j);u(h,{findings:C,counts:_(C),...ge(j),documentName:c,seq:h,source:"validate",analyzedAt:Date.now()})}catch{}finally{l.current===x&&(l.current=null)}},[u,o,t,s,n]);return{analyze:p.useCallback((c,f,v)=>{if(i.current!==null&&(clearTimeout(i.current),i.current=null),v!=null&&v.immediate){g(c,f);return}i.current=setTimeout(()=>{i.current=null,g(c,f)},pe)},[g]),cancel:a}}export{Ie as E,Ae as a,le as b,_ as c,Ee as d,Se as e,M as f,xe as g,de as i,fe as n,ae as p,ue as s,we as u};
6
- //# sourceMappingURL=useQualityAnalysis-CowAaoA9.js.map
6
+ //# sourceMappingURL=useQualityAnalysis-BFG1bJDp.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useQualityAnalysis-CowAaoA9.js","sources":["../../../src/client/lib/quality-findings.ts","../../../src/client/lib/quality-policy.ts","../../../src/client/lib/quality-profiles.ts","../../../src/client/lib/quality-sequence.ts","../../../src/client/hooks/useQualityAnalysis.ts"],"sourcesContent":["/**\n * The playground's own model of a quality finding.\n *\n * The server hands the same finding over in two shapes: `/validate` returns\n * rich `QualityDiagnostic` objects, while `/generate` flattens each one into a\n * `GenerationWarning` whose real fields live in `context` and whose severity is\n * squashed to `warning | info`. Normalising both here keeps that asymmetry out\n * of the panel, the store and the Monaco markers.\n */\n\nexport type QualitySeverity = 'error' | 'warning' | 'info';\n\nexport type QualityCertainty =\n | 'deterministic'\n | 'measured'\n | 'estimated'\n | 'rendered'\n | 'evaluative';\n\nexport interface QualityFixOp {\n op: 'add' | 'replace' | 'remove' | 'move' | 'copy' | 'test';\n path: string;\n value?: unknown;\n from?: string;\n}\n\nexport interface QualityEvidence {\n actual?: unknown;\n expected?: unknown;\n unit?: string;\n [key: string]: unknown;\n}\n\nexport interface QualityFinding {\n /** Stable within one analysis; used as a React key and for fix dedup. */\n id: string;\n code: string;\n message: string;\n /** RFC 6901 pointer into the authored document. */\n path: string;\n severity: QualitySeverity;\n ruleId?: string;\n category?: string;\n certainty?: QualityCertainty;\n blocking?: boolean;\n suggestion?: string;\n relatedPaths?: readonly string[];\n evidence?: QualityEvidence;\n fixes?: readonly QualityFixOp[];\n}\n\nexport interface GenerationWarningLike {\n component: string;\n message: string;\n severity?: 'warning' | 'info';\n context?: Record<string, unknown>;\n}\n\nconst SEVERITIES: readonly QualitySeverity[] = ['error', 'warning', 'info'];\n\nconst SEVERITY_RANK: Record<string, number> = {\n error: 0,\n warning: 1,\n info: 2,\n};\n\n/**\n * Anything that is not one of the three legal severities sorts last rather\n * than throwing, so a future server-side severity cannot crash the panel.\n */\nconst UNRANKED = 3;\n\nfunction rankOf(severity: unknown): number {\n return typeof severity === 'string' && severity in SEVERITY_RANK\n ? (SEVERITY_RANK[severity] as number)\n : UNRANKED;\n}\n\nconst CERTAINTIES: readonly QualityCertainty[] = [\n 'deterministic',\n 'measured',\n 'estimated',\n 'rendered',\n 'evaluative',\n];\n\nconst FIX_OPS: readonly QualityFixOp['op'][] = [\n 'add',\n 'replace',\n 'remove',\n 'move',\n 'copy',\n 'test',\n];\n\n/** Findings with no category are grouped under this bucket. */\nconst UNCATEGORIZED = 'other';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The same test as `isRecord` but without the type predicate, for values that\n * are already typed and only need the runtime guard — narrowing them to\n * `Record<string, unknown>` would throw away the declared shape.\n */\nfunction looksLikeObject(value: unknown): boolean {\n return isRecord(value);\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction asSeverity(value: unknown): QualitySeverity | undefined {\n return typeof value === 'string' &&\n (SEVERITIES as readonly string[]).includes(value)\n ? (value as QualitySeverity)\n : undefined;\n}\n\nfunction asCertainty(value: unknown): QualityCertainty | undefined {\n return typeof value === 'string' &&\n (CERTAINTIES as readonly string[]).includes(value)\n ? (value as QualityCertainty)\n : undefined;\n}\n\nfunction asStringArray(value: unknown): readonly string[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const strings = value.filter((entry): entry is string => {\n return typeof entry === 'string';\n });\n return strings.length > 0 ? strings : undefined;\n}\n\nfunction asEvidence(value: unknown): QualityEvidence | undefined {\n return isRecord(value) ? (value as QualityEvidence) : undefined;\n}\n\n/**\n * A malformed patch operation is worse than none: the fix button would apply\n * garbage to the author's document, so anything without a legal `op` and a\n * string `path` is discarded rather than passed along.\n */\nfunction asFixes(value: unknown): readonly QualityFixOp[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const fixes: QualityFixOp[] = [];\n for (const entry of value) {\n if (!isRecord(entry)) continue;\n const op = entry['op'];\n const path = entry['path'];\n if (typeof op !== 'string' || typeof path !== 'string') continue;\n if (!(FIX_OPS as readonly string[]).includes(op)) continue;\n const fix: QualityFixOp = { op: op as QualityFixOp['op'], path };\n if ('value' in entry) fix.value = entry['value'];\n const from = asString(entry['from']);\n if (from !== undefined) fix.from = from;\n fixes.push(fix);\n }\n return fixes.length > 0 ? fixes : undefined;\n}\n\n/**\n * The generate path double-encodes the code: the message arrives as\n * \"[W_QUALITY_FONT_SIZE_MIN] Body text is 5pt\" while the code is also in\n * `context.code`. Stripping the prefix keeps the panel from showing the code\n * twice, but only when the two agree — an unrelated bracketed prefix is part\n * of the rule's own wording and must survive.\n */\nfunction stripCodePrefix(message: string, code: string | undefined): string {\n if (!code) return message;\n const prefix = `[${code}] `;\n return message.startsWith(prefix) ? message.slice(prefix.length) : message;\n}\n\n/**\n * Ids must survive a re-render of the same analysis, so they are derived from\n * the finding rather than from a counter or a random value. The index breaks\n * ties between findings that share a code and a path.\n */\nfunction makeId(code: string, path: string, index: number): string {\n return `${code || 'quality'}:${path}:${index}`;\n}\n\nfunction buildFinding(\n fields: {\n code: unknown;\n message: unknown;\n path: unknown;\n severity: QualitySeverity;\n ruleId: unknown;\n category: unknown;\n certainty: unknown;\n blocking: unknown;\n suggestion: unknown;\n relatedPaths: unknown;\n evidence: unknown;\n fixes: unknown;\n },\n index: number\n): QualityFinding | undefined {\n const code = asString(fields.code) ?? '';\n const rawMessage = asString(fields.message) ?? '';\n // Nothing to render and nothing to look up: the entry is not a finding.\n if (!code && !rawMessage) return undefined;\n\n const path = asString(fields.path) ?? '';\n const finding: QualityFinding = {\n id: makeId(code, path, index),\n code,\n // A finding row with no text at all is unreadable, so a code-only entry\n // shows its code rather than rendering blank.\n message: stripCodePrefix(rawMessage, code) || code,\n path,\n severity: fields.severity,\n };\n\n const ruleId = asString(fields.ruleId);\n if (ruleId !== undefined) finding.ruleId = ruleId;\n\n const category = asString(fields.category);\n if (category !== undefined) finding.category = category;\n\n const certainty = asCertainty(fields.certainty);\n if (certainty !== undefined) finding.certainty = certainty;\n\n if (typeof fields.blocking === 'boolean') finding.blocking = fields.blocking;\n\n const suggestion = asString(fields.suggestion);\n if (suggestion !== undefined) finding.suggestion = suggestion;\n\n const relatedPaths = asStringArray(fields.relatedPaths);\n if (relatedPaths !== undefined) finding.relatedPaths = relatedPaths;\n\n const evidence = asEvidence(fields.evidence);\n if (evidence !== undefined) finding.evidence = evidence;\n\n const fixes = asFixes(fields.fixes);\n if (fixes !== undefined) finding.fixes = fixes;\n\n return finding;\n}\n\n/** Split a generate response's warnings into quality findings and everything else. */\nexport function splitQualityWarnings(\n warnings: readonly GenerationWarningLike[] | null | undefined\n): { findings: QualityFinding[]; others: GenerationWarningLike[] } {\n const findings: QualityFinding[] = [];\n const others: GenerationWarningLike[] = [];\n if (!Array.isArray(warnings)) return { findings, others };\n\n for (const warning of warnings) {\n // The array is typed, but it comes off the wire — a null slot is possible.\n if (!looksLikeObject(warning)) continue;\n if (warning.component !== 'quality') {\n others.push(warning);\n continue;\n }\n\n const context = isRecord(warning.context) ? warning.context : {};\n // `context.originalSeverity` is the only place an 'error' survives; the\n // top-level severity was already coerced to warning | info by the server.\n const severity =\n asSeverity(context['originalSeverity']) ??\n asSeverity(warning.severity) ??\n 'warning';\n\n const finding = buildFinding(\n {\n code: context['code'],\n message: warning.message,\n path: context['path'],\n severity,\n ruleId: context['ruleId'],\n category: context['category'],\n certainty: context['certainty'],\n blocking: context['blocking'],\n suggestion: context['suggestion'],\n relatedPaths: context['relatedPaths'],\n evidence: context['evidence'],\n fixes: context['fixes'],\n },\n findings.length\n );\n if (finding) findings.push(finding);\n }\n\n return { findings, others };\n}\n\n/** Findings from a /validate response's qualityAnalysis object. */\nexport function findingsFromAnalysis(analysis: unknown): QualityFinding[] {\n if (!isRecord(analysis)) return [];\n const diagnostics = analysis['diagnostics'];\n if (!Array.isArray(diagnostics)) return [];\n\n const findings: QualityFinding[] = [];\n for (const diagnostic of diagnostics) {\n if (!isRecord(diagnostic)) continue;\n const finding = buildFinding(\n {\n code: diagnostic['code'],\n message: diagnostic['message'],\n path: diagnostic['path'],\n severity: asSeverity(diagnostic['severity']) ?? 'warning',\n ruleId: diagnostic['ruleId'],\n category: diagnostic['category'],\n certainty: diagnostic['certainty'],\n blocking: diagnostic['blocking'],\n suggestion: diagnostic['suggestion'],\n relatedPaths: diagnostic['relatedPaths'],\n evidence: diagnostic['evidence'],\n fixes: diagnostic['fixes'],\n },\n findings.length\n );\n if (finding) findings.push(finding);\n }\n return findings;\n}\n\nexport function countBySeverity(findings: readonly QualityFinding[]): {\n error: number;\n warning: number;\n info: number;\n} {\n const counts = { error: 0, warning: 0, info: 0 };\n if (!Array.isArray(findings)) return counts;\n for (const finding of findings) {\n const severity = asSeverity(finding?.severity);\n if (severity) counts[severity] += 1;\n }\n return counts;\n}\n\n/** error < warning < info, then by path, then by code. For Array.sort. */\nexport function compareFindings(a: QualityFinding, b: QualityFinding): number {\n const rankA = rankOf(a?.severity);\n const rankB = rankOf(b?.severity);\n if (rankA !== rankB) return rankA - rankB;\n const pathA = a?.path ?? '';\n const pathB = b?.path ?? '';\n if (pathA !== pathB) return pathA < pathB ? -1 : 1;\n const codeA = a?.code ?? '';\n const codeB = b?.code ?? '';\n if (codeA === codeB) return 0;\n return codeA < codeB ? -1 : 1;\n}\n\n/** Groups sorted by worst severity within the group, then category name. */\nexport function groupByCategory(\n findings: readonly QualityFinding[]\n): Array<{ category: string; findings: QualityFinding[] }> {\n const groups = new Map<string, QualityFinding[]>();\n if (!Array.isArray(findings)) return [];\n\n for (const finding of findings) {\n if (!finding) continue;\n const category = finding.category || UNCATEGORIZED;\n const bucket = groups.get(category);\n if (bucket) bucket.push(finding);\n else groups.set(category, [finding]);\n }\n\n const worstRank = (group: readonly QualityFinding[]): number => {\n return group.reduce((worst, finding) => {\n const rank = rankOf(finding.severity);\n return rank < worst ? rank : worst;\n }, UNRANKED);\n };\n\n return Array.from(groups.entries())\n .map(([category, bucket]) => ({\n category,\n findings: [...bucket].sort(compareFindings),\n }))\n .sort((a, b) => {\n const rankDelta = worstRank(a.findings) - worstRank(b.findings);\n if (rankDelta !== 0) return rankDelta;\n if (a.category === b.category) return 0;\n return a.category < b.category ? -1 : 1;\n });\n}\n\nexport function filterByMinSeverity(\n findings: readonly QualityFinding[],\n min: QualitySeverity\n): QualityFinding[] {\n if (!Array.isArray(findings)) return [];\n // An unknown threshold must not silently hide findings, so it lets all pass.\n const limit = asSeverity(min) ? rankOf(min) : UNRANKED;\n return findings.filter((finding) => rankOf(finding?.severity) <= limit);\n}\n\n/** Human label for a certainty, e.g. 'measured' -> 'Measured'. */\nexport function certaintyLabel(\n c: QualityCertainty | undefined\n): string | undefined {\n const certainty = asCertainty(c);\n if (!certainty) return undefined;\n return certainty.charAt(0).toUpperCase() + certainty.slice(1);\n}\n","/**\n * The run policy an author writes by hand.\n *\n * The profile and gate controls cover the two settings most runs need; this\n * covers the rest of the contract — per-rule severity, enable/disable and\n * parameters, suppressions, and the diagnostic budget — which until now was\n * reachable only through the CLI's `--quality-policy` file.\n *\n * `gate` is deliberately NOT accepted here. It has a control of its own, and\n * two writable sources for one field means the dropdown either lies or silently\n * loses, so naming it in the JSON is reported as a mistake instead.\n */\n\nimport { rulesForFormat } from './quality-rules';\n\nexport interface QualityRuleConfiguration {\n enabled?: boolean;\n severity?: 'error' | 'warning' | 'info';\n parameters?: Record<string, unknown>;\n}\n\nexport interface QualitySuppression {\n ruleId?: string;\n code?: string;\n path?: string;\n pathMatch?: 'exact' | 'subtree';\n reason: string;\n}\n\nexport interface EditableQualityPolicy {\n rules?: Record<string, QualityRuleConfiguration>;\n suppressions?: QualitySuppression[];\n maxDiagnostics?: number;\n onRuleError?: 'continue' | 'throw';\n}\n\nexport type ParsedQualityPolicy =\n | { ok: true; policy: EditableQualityPolicy | undefined }\n | { ok: false; error: string };\n\n/** What a fresh editor opens on: valid, inert, and a shape to copy. */\nexport const EMPTY_POLICY_TEXT = `{\n \"rules\": {},\n \"suppressions\": []\n}\n`;\n\nconst SEVERITIES = new Set(['error', 'warning', 'info']);\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parse and check a hand-written policy.\n *\n * Errors are values and name the offending key, because this text is typed by\n * hand and the server's own rejection would arrive as a 400 the panel reports\n * far from the field that caused it.\n */\nexport function parseQualityPolicy(\n text: string | undefined\n): ParsedQualityPolicy {\n const trimmed = (text ?? '').trim();\n if (!trimmed) return { ok: true, policy: undefined };\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch (error) {\n return { ok: false, error: `Not valid JSON: ${(error as Error).message}` };\n }\n if (!isPlainObject(parsed)) {\n return { ok: false, error: 'A policy must be a JSON object.' };\n }\n\n if ('gate' in parsed) {\n return {\n ok: false,\n error: 'Remove \"gate\" — it is set by the Gate control above.',\n };\n }\n if ('profile' in parsed) {\n return {\n ok: false,\n error: 'Remove \"profile\" — it is set by the Profile control above.',\n };\n }\n\n // Every key below is read by name, so anything else would be dropped in\n // silence — `{\"maxDiagnostic\": 0}` parsed as a valid empty policy and the\n // budget the author asked for never applied. Say so instead.\n const EDITABLE_KEYS = new Set([\n 'rules',\n 'suppressions',\n 'maxDiagnostics',\n 'onRuleError',\n ]);\n const unknownKey = Object.keys(parsed).find((key) => !EDITABLE_KEYS.has(key));\n if (unknownKey !== undefined) {\n return {\n ok: false,\n error: `Unknown key \"${unknownKey}\". A policy takes: ${[...EDITABLE_KEYS].join(', ')}.`,\n };\n }\n\n const known = new Set(rulesForFormat().map((rule) => rule.id));\n const policy: EditableQualityPolicy = {};\n\n if (parsed.rules !== undefined) {\n if (!isPlainObject(parsed.rules)) {\n return {\n ok: false,\n error: '\"rules\" must be an object keyed by rule id.',\n };\n }\n for (const [ruleId, configuration] of Object.entries(parsed.rules)) {\n if (!known.has(ruleId)) {\n return {\n ok: false,\n error: `Unknown rule \"${ruleId}\". This format ships: ${[...known].join(', ')}.`,\n };\n }\n if (!isPlainObject(configuration)) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\" must be an object, e.g. { \"severity\": \"warning\" }.`,\n };\n }\n if (\n configuration.severity !== undefined &&\n !SEVERITIES.has(configuration.severity as string)\n ) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\" has an invalid severity. Use \"error\", \"warning\" or \"info\".`,\n };\n }\n if (\n configuration.enabled !== undefined &&\n typeof configuration.enabled !== 'boolean'\n ) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\": \"enabled\" must be true or false.`,\n };\n }\n if (\n configuration.parameters !== undefined &&\n !isPlainObject(configuration.parameters)\n ) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\": \"parameters\" must be an object.`,\n };\n }\n }\n policy.rules = parsed.rules as Record<string, QualityRuleConfiguration>;\n }\n\n if (parsed.suppressions !== undefined) {\n if (!Array.isArray(parsed.suppressions)) {\n return { ok: false, error: '\"suppressions\" must be an array.' };\n }\n for (const [index, entry] of parsed.suppressions.entries()) {\n if (!isPlainObject(entry)) {\n return { ok: false, error: `Suppression ${index} must be an object.` };\n }\n // The contract makes `reason` mandatory on purpose: a muted finding that\n // nobody has to justify is how a rule quietly stops being enforced.\n if (typeof entry.reason !== 'string' || !entry.reason.trim()) {\n return {\n ok: false,\n error: `Suppression ${index} needs a \"reason\" saying why the finding is acceptable.`,\n };\n }\n if (\n entry.pathMatch !== undefined &&\n entry.pathMatch !== 'exact' &&\n entry.pathMatch !== 'subtree'\n ) {\n return {\n ok: false,\n error: `Suppression ${index}: \"pathMatch\" must be \"exact\" or \"subtree\".`,\n };\n }\n }\n policy.suppressions = parsed.suppressions as QualitySuppression[];\n }\n\n if (parsed.maxDiagnostics !== undefined) {\n if (\n !Number.isInteger(parsed.maxDiagnostics) ||\n (parsed.maxDiagnostics as number) < 0\n ) {\n return {\n ok: false,\n error: '\"maxDiagnostics\" must be a whole number, zero or more.',\n };\n }\n policy.maxDiagnostics = parsed.maxDiagnostics as number;\n }\n\n if (parsed.onRuleError !== undefined) {\n if (parsed.onRuleError !== 'continue' && parsed.onRuleError !== 'throw') {\n return {\n ok: false,\n error: '\"onRuleError\" must be \"continue\" or \"throw\".',\n };\n }\n policy.onRuleError = parsed.onRuleError;\n }\n\n const empty =\n (policy.rules === undefined || Object.keys(policy.rules).length === 0) &&\n (policy.suppressions === undefined || policy.suppressions.length === 0) &&\n policy.maxDiagnostics === undefined &&\n policy.onRuleError === undefined;\n\n return { ok: true, policy: empty ? undefined : policy };\n}\n","/**\n * The shipped quality profiles, and the one place a stored profile id is\n * turned into a request.\n *\n * Settings persist to a single localStorage key shared by both playgrounds\n * (`window.__JTO_FORMAT__` picks the format; the origin is the same), so a\n * profile chosen on the DOCX playground is still in storage when the PPTX one\n * opens. Sending it would be worse than useless: the client stamps\n * `formats: [FORMAT]` itself, so the server's compatibility check passes, the\n * id matches no registered profile, and the analysis silently runs on defaults\n * while the panel and the response both report the profile as active.\n * Resolution therefore happens here, at the point of use, rather than in a\n * component that only guards while it is mounted.\n */\n\nimport { FORMAT, type FormatName } from './env';\nimport { parseQualityPolicy } from './quality-policy';\n\nexport interface QualityProfileChoice {\n id: string;\n label: string;\n description: string;\n}\n\nexport const QUALITY_PROFILES: Record<\n FormatName,\n readonly QualityProfileChoice[]\n> = {\n docx: [\n {\n id: 'executive-report',\n label: 'Executive report',\n description: 'Short decision document with strict outline continuity',\n },\n {\n id: 'technical-report',\n label: 'Technical report',\n description: 'Portable professional report defaults',\n },\n {\n id: 'legal-appendix',\n label: 'Legal appendix',\n description: 'Dense appendix: preserve integrity without editorial taste',\n },\n ],\n pptx: [\n {\n id: 'executive-presentation',\n label: 'Executive presentation',\n description: 'Decision deck optimized for scan speed and projection',\n },\n {\n id: 'technical-presentation',\n label: 'Technical presentation',\n description: 'Portable professional presentation defaults',\n },\n ],\n};\n\n/** The profiles offerable for the format this playground is serving. */\nexport function profilesForFormat(): readonly QualityProfileChoice[] {\n return QUALITY_PROFILES[FORMAT];\n}\n\n/** The profile id stored for this format, if it still names a shipped one. */\nexport function storedProfileId(\n ids: Partial<Record<FormatName, string>> | undefined\n): string | undefined {\n const id = ids?.[FORMAT];\n return isProfileForFormat(id) ? id : undefined;\n}\n\n/** True when `id` names a profile the current format actually ships. */\nexport function isProfileForFormat(id: string | undefined): id is string {\n return id !== undefined && QUALITY_PROFILES[FORMAT].some((p) => p.id === id);\n}\n\nexport interface QualityRequestOptions {\n profile?: { id: string; formats: string[] };\n policy?: Record<string, unknown>;\n}\n\n/**\n * Build the `options.quality` payload for a generate or validate request.\n *\n * Returns undefined when neither a profile nor a gate was asked for, so the\n * request omits the key entirely and the server keeps its own defaults. A\n * profile id belonging to the other format is dropped rather than sent.\n */\nexport function buildQualityOptions(\n profileId: string | undefined,\n gate: string | undefined,\n policyText?: string\n): QualityRequestOptions | undefined {\n const options: QualityRequestOptions = {};\n if (isProfileForFormat(profileId)) {\n options.profile = { id: profileId, formats: [FORMAT] };\n }\n // A policy still being typed is not sent at all: the run keeps working under\n // the last good one rather than failing on every unbalanced brace.\n const parsed = parseQualityPolicy(policyText);\n const authored = parsed.ok ? parsed.policy : undefined;\n const hasGate = gate === 'error' || gate === 'warning' || gate === 'info';\n if (authored || hasGate) {\n options.policy = {\n ...(authored ?? {}),\n // The Gate control owns this field; `parseQualityPolicy` rejects a policy\n // that also names it, so there is nothing here to overwrite.\n ...(hasGate && { gate }),\n };\n }\n return options.profile || options.policy ? options : undefined;\n}\n","/**\n * Ordering tickets for quality analyses.\n *\n * Three independent producers write the same store slice: the editor's\n * debounced validate pass, the preview's immediate re-analysis after a fix,\n * and the build's own findings. Each aborts only its own in-flight request, so\n * per-request cancellation cannot order them against each other — a ~10s build\n * started before an edit still resolves after the validate pass that overtook\n * it, and would otherwise reinstate findings for text the editor no longer\n * holds.\n *\n * Every producer takes a ticket before it starts and drops its result if a\n * higher one has already landed. Tickets are process-wide because the writers\n * are separate hook instances in one bundle; a per-instance counter would\n * compare numbers that mean nothing to each other.\n */\n\nimport type { QualityState } from '../store/output-store';\n\nlet counter = 0;\n\n/** Monotonic, allocated at the moment work starts, not when it finishes. */\nexport function nextQualityTicket(): number {\n counter += 1;\n return counter;\n}\n\n/** True when a newer analysis has already been committed. */\nexport function isStaleQualityTicket(\n current: QualityState | null | undefined,\n ticket: number\n): boolean {\n return current != null && current.seq > ticket;\n}\n","import { useCallback, useContext, useEffect, useRef } from 'react';\nimport { API_ENDPOINTS } from '../config/api';\nimport { FORMAT } from '../lib/env';\nimport { countBySeverity, findingsFromAnalysis } from '../lib/quality-findings';\nimport { buildQualityOptions, storedProfileId } from '../lib/quality-profiles';\nimport {\n isStaleQualityTicket,\n nextQualityTicket,\n} from '../lib/quality-sequence';\nimport type { QualityState } from '../store/output-store';\nimport {\n OutputStoreContext,\n useOutputStore,\n} from '../store/output-store-provider';\nimport { useSettingsStore } from '../store/settings-store-provider';\nimport { isJsonMediaType } from './usePresentationGenerator';\n\n/**\n * Long enough that a burst of typing costs one request rather than one per\n * keystroke, short enough that a pause reads as \"the panel just updated\".\n *\n * The upper bound is not taste: `/api/*` rate-limits on `METHOD:path`, and in\n * production that budget is 100 requests per 15 minutes (server config\n * `RATE_LIMIT_MAX`). One analysis per typing pause has to stay well under\n * ~7 requests/minute sustained or the panel starts answering 429 to the author\n * who is typing the most.\n */\nconst ANALYSIS_DEBOUNCE_MS = 1500;\n\ninterface ValidateResponseBody {\n success?: boolean;\n data?: { valid?: boolean; qualityAnalysis?: unknown };\n error?: string;\n /** `QUALITY_GATE_FAILED` distinguishes a real rejection from a 429 or a 500. */\n code?: string;\n /** Present on a gate failure: the analysis that tripped it. */\n quality?: unknown;\n}\n\n/** The JSON body, or undefined when the response is not JSON at all. */\nasync function readJsonBody(\n response: Response\n): Promise<ValidateResponseBody | undefined> {\n // A proxy that never reached the API answers with its own HTML error page,\n // and parsing that unguarded is what turns a dead backend into\n // `Unexpected token '<'` instead of the status it actually was.\n if (!isJsonMediaType(response.headers.get('content-type'))) return undefined;\n try {\n return (await response.json()) as ValidateResponseBody;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The analysis-level fields, read one at a time.\n *\n * Spreading the server's object into the store would let an unexpected type\n * through to the panel, where a `blocked` that is a string reads as truthy and\n * a `profileId` that is an object renders as `[object Object]`.\n */\nfunction readAnalysisFlags(\n analysis: unknown\n): Pick<QualityState, 'profileId' | 'blocked' | 'truncated'> {\n if (!analysis || typeof analysis !== 'object') return {};\n const source = analysis as Record<string, unknown>;\n return {\n ...(typeof source.profileId === 'string'\n ? { profileId: source.profileId }\n : {}),\n ...(typeof source.blocked === 'boolean' ? { blocked: source.blocked } : {}),\n ...(typeof source.truncated === 'boolean'\n ? { truncated: source.truncated }\n : {}),\n };\n}\n\n/** The API's own message when there is one; the status is all that is left. */\nfunction describeFailure(\n response: Response,\n body: ValidateResponseBody | undefined\n): string {\n if (body?.error) return body.error;\n return `Quality analysis failed (${response.status}${\n response.statusText ? ` ${response.statusText}` : ''\n }).`;\n}\n\n/**\n * Re-run the quality analysis as the document changes, without rendering it.\n *\n * `/validate` returns the rich diagnostics `/generate` flattens into warnings,\n * so this is what keeps the panel current between builds — the author sees the\n * findings move while editing rather than only after asking for bytes.\n */\nexport function useQualityAnalysis(): {\n analyze: (\n documentName: string,\n documentText: string,\n options?: { immediate?: boolean }\n ) => void;\n cancel: () => void;\n} {\n const setOutput = useOutputStore((state) => state.setOutput);\n const outputStore = useContext(OutputStoreContext)!;\n // Read here rather than inside the request so a settings change re-creates\n // `analyze`, and a caller holding the old one cannot keep analysing under a\n // profile the author has already switched away from.\n const profileIds = useSettingsStore((state) => state.qualityProfileIds);\n const profileId = storedProfileId(profileIds);\n const gate = useSettingsStore((state) => state.qualityGate);\n const policyText = useSettingsStore(\n (state) => state.qualityPolicies?.[FORMAT]\n );\n\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n\n const cancel = useCallback(() => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n abortRef.current?.abort();\n abortRef.current = null;\n }, []);\n\n // An analysis that outlives its component would resolve into a store nobody\n // is reading, and worse, overwrite findings a later mount had already put\n // there.\n useEffect(() => cancel, [cancel]);\n\n /**\n * Commit a result unless a newer analysis has already landed.\n *\n * Aborting only reaches this hook instance's own request, and there are\n * several instances plus the build writing the same slice, so the ticket is\n * what actually orders them.\n */\n const commit = useCallback(\n (ticket: number, next: QualityState) => {\n if (isStaleQualityTicket(outputStore.getState().quality, ticket)) return;\n setOutput({ quality: next });\n },\n [outputStore, setOutput]\n );\n\n const run = useCallback(\n async (documentName: string, documentText: string) => {\n let jsonDefinition: unknown;\n try {\n jsonDefinition = JSON.parse(documentText);\n } catch {\n // Half-typed JSON is the normal state of an editor, not something to\n // report. Keep the previous findings on screen: blanking the panel on\n // every unbalanced brace is worse than showing findings one keystroke\n // out of date.\n return;\n }\n\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n const ticket = nextQualityTicket();\n\n const quality = buildQualityOptions(profileId, gate, policyText);\n\n try {\n const response = await fetch(API_ENDPOINTS.validate, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonDefinition,\n options: quality ? { quality } : {},\n }),\n signal: controller.signal,\n });\n const body = await readJsonBody(response);\n\n // A superseded request has already been replaced by a newer one, whose\n // answer is the current one; writing this stale result would undo it.\n if (controller.signal.aborted) return;\n\n if (!response.ok) {\n const message = describeFailure(response, body);\n if (body?.code === 'QUALITY_GATE_FAILED') {\n // A real rejection, and it carries the diagnostics that caused it.\n const findings = findingsFromAnalysis(body.quality);\n commit(ticket, {\n findings,\n counts: countBySeverity(findings),\n documentName,\n seq: ticket,\n blocked: true,\n source: 'validate',\n analyzedAt: Date.now(),\n gateError: message,\n });\n return;\n }\n // Everything else — 429 from the rate limiter, 413, a 500, a dead\n // proxy — says nothing about the document. Reporting it as a gate\n // failure would tell an author with no gate configured that their\n // document was blocked, and discarding the findings alongside it\n // would take away the only thing on screen that was still true.\n // Keeping the findings is only right while they still describe this\n // document. Across a tab switch they describe the previous one, and\n // every finding is a JSON Pointer into a file that is no longer on\n // screen — \"Apply fix\" would patch the wrong document. A stale\n // `blocked` is worse still: a gate rejection from before the switch\n // would keep the new document blocked on a transport error.\n const stored = outputStore.getState().quality;\n const previous =\n stored && stored.documentName === documentName ? stored : undefined;\n commit(ticket, {\n ...(previous ?? {\n findings: [],\n counts: { error: 0, warning: 0, info: 0 },\n source: 'validate' as const,\n }),\n documentName,\n seq: ticket,\n analyzedAt: Date.now(),\n blocked: previous?.blocked ?? false,\n gateError: undefined,\n analysisError: message,\n });\n return;\n }\n\n // `success` is false for a schema-invalid document and for a blocked\n // one, neither of which is a failed request — the analysis, when there\n // is one, is still in `data`.\n const analysis = body?.data?.qualityAnalysis;\n const findings = findingsFromAnalysis(analysis);\n commit(ticket, {\n findings,\n counts: countBySeverity(findings),\n ...readAnalysisFlags(analysis),\n documentName,\n seq: ticket,\n source: 'validate',\n analyzedAt: Date.now(),\n });\n } catch {\n // An abort lands here, and so does a dropped connection. Neither says\n // anything about the document, so the last analysis stands.\n } finally {\n if (abortRef.current === controller) abortRef.current = null;\n }\n },\n [commit, gate, outputStore, policyText, profileId]\n );\n\n const analyze = useCallback(\n (\n documentName: string,\n documentText: string,\n options?: { immediate?: boolean }\n ) => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n if (options?.immediate) {\n void run(documentName, documentText);\n return;\n }\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n void run(documentName, documentText);\n }, ANALYSIS_DEBOUNCE_MS);\n },\n [run]\n );\n\n return { analyze, cancel };\n}\n"],"names":["SEVERITIES","SEVERITY_RANK","UNRANKED","rankOf","severity","CERTAINTIES","FIX_OPS","UNCATEGORIZED","isRecord","value","looksLikeObject","asString","asSeverity","asCertainty","asStringArray","strings","entry","asEvidence","asFixes","fixes","op","path","fix","from","stripCodePrefix","message","code","prefix","makeId","index","buildFinding","fields","rawMessage","finding","ruleId","category","certainty","suggestion","relatedPaths","evidence","splitQualityWarnings","warnings","findings","others","warning","context","findingsFromAnalysis","analysis","diagnostics","diagnostic","countBySeverity","counts","compareFindings","a","b","rankA","rankB","pathA","pathB","codeA","codeB","groupByCategory","groups","bucket","worstRank","group","worst","rank","rankDelta","filterByMinSeverity","min","limit","certaintyLabel","c","EMPTY_POLICY_TEXT","isPlainObject","parseQualityPolicy","text","trimmed","parsed","error","EDITABLE_KEYS","unknownKey","key","known","rulesForFormat","rule","policy","configuration","QUALITY_PROFILES","storedProfileId","ids","id","FORMAT","isProfileForFormat","p","buildQualityOptions","profileId","gate","policyText","options","authored","hasGate","counter","nextQualityTicket","isStaleQualityTicket","current","ticket","ANALYSIS_DEBOUNCE_MS","readJsonBody","response","isJsonMediaType","readAnalysisFlags","source","describeFailure","body","useQualityAnalysis","setOutput","useOutputStore","state","outputStore","useContext","OutputStoreContext","profileIds","useSettingsStore","_a","timerRef","useRef","abortRef","cancel","useCallback","useEffect","commit","next","run","documentName","documentText","jsonDefinition","controller","quality","API_ENDPOINTS","stored","previous","_b"],"mappings":"qNA0DA,MAAMA,EAAyC,CAAC,QAAS,UAAW,MAAM,EAEpEC,EAAwC,CAC5C,MAAO,EACP,QAAS,EACT,KAAM,CACR,EAMMC,EAAW,EAEjB,SAASC,EAAOC,EAA2B,CACzC,OAAO,OAAOA,GAAa,UAAYA,KAAYH,EAC9CA,EAAcG,CAAQ,EACvBF,CACN,CAEA,MAAMG,EAA2C,CAC/C,gBACA,WACA,YACA,WACA,YACF,EAEMC,EAAyC,CAC7C,MACA,UACA,SACA,OACA,OACA,MACF,EAGMC,EAAgB,QAEtB,SAASC,EAASC,EAAkD,CAClE,OAAO,OAAOA,GAAU,UAAYA,IAAU,MAAQ,CAAC,MAAM,QAAQA,CAAK,CAC5E,CAOA,SAASC,EAAgBD,EAAyB,CAChD,OAAOD,EAASC,CAAK,CACvB,CAEA,SAASE,EAASF,EAAoC,CACpD,OAAO,OAAOA,GAAU,SAAWA,EAAQ,MAC7C,CAEA,SAASG,EAAWH,EAA6C,CAC/D,OAAO,OAAOA,GAAU,UACrBT,EAAiC,SAASS,CAAK,EAC7CA,EACD,MACN,CAEA,SAASI,EAAYJ,EAA8C,CACjE,OAAO,OAAOA,GAAU,UACrBJ,EAAkC,SAASI,CAAK,EAC9CA,EACD,MACN,CAEA,SAASK,GAAcL,EAA+C,CACpE,GAAI,CAAC,MAAM,QAAQA,CAAK,EAAG,OAC3B,MAAMM,EAAUN,EAAM,OAAQO,GACrB,OAAOA,GAAU,QACzB,EACD,OAAOD,EAAQ,OAAS,EAAIA,EAAU,MACxC,CAEA,SAASE,GAAWR,EAA6C,CAC/D,OAAOD,EAASC,CAAK,EAAKA,EAA4B,MACxD,CAOA,SAASS,GAAQT,EAAqD,CACpE,GAAI,CAAC,MAAM,QAAQA,CAAK,EAAG,OAC3B,MAAMU,EAAwB,CAAA,EAC9B,UAAWH,KAASP,EAAO,CACzB,GAAI,CAACD,EAASQ,CAAK,EAAG,SACtB,MAAMI,EAAKJ,EAAM,GACXK,EAAOL,EAAM,KAEnB,GADI,OAAOI,GAAO,UAAY,OAAOC,GAAS,UAC1C,CAAEf,EAA8B,SAASc,CAAE,EAAG,SAClD,MAAME,EAAoB,CAAE,GAAAF,EAA8B,KAAAC,CAAA,EACtD,UAAWL,IAAOM,EAAI,MAAQN,EAAM,OACxC,MAAMO,EAAOZ,EAASK,EAAM,IAAO,EAC/BO,IAAS,SAAWD,EAAI,KAAOC,GACnCJ,EAAM,KAAKG,CAAG,CAChB,CACA,OAAOH,EAAM,OAAS,EAAIA,EAAQ,MACpC,CASA,SAASK,GAAgBC,EAAiBC,EAAkC,CAC1E,GAAI,CAACA,EAAM,OAAOD,EAClB,MAAME,EAAS,IAAID,CAAI,KACvB,OAAOD,EAAQ,WAAWE,CAAM,EAAIF,EAAQ,MAAME,EAAO,MAAM,EAAIF,CACrE,CAOA,SAASG,GAAOF,EAAcL,EAAcQ,EAAuB,CACjE,MAAO,GAAGH,GAAQ,SAAS,IAAIL,CAAI,IAAIQ,CAAK,EAC9C,CAEA,SAASC,EACPC,EAcAF,EAC4B,CAC5B,MAAMH,EAAOf,EAASoB,EAAO,IAAI,GAAK,GAChCC,EAAarB,EAASoB,EAAO,OAAO,GAAK,GAE/C,GAAI,CAACL,GAAQ,CAACM,EAAY,OAE1B,MAAMX,EAAOV,EAASoB,EAAO,IAAI,GAAK,GAChCE,EAA0B,CAC9B,GAAIL,GAAOF,EAAML,EAAMQ,CAAK,EAC5B,KAAAH,EAGA,QAASF,GAAgBQ,EAAYN,CAAI,GAAKA,EAC9C,KAAAL,EACA,SAAUU,EAAO,QAAA,EAGbG,EAASvB,EAASoB,EAAO,MAAM,EACjCG,IAAW,SAAWD,EAAQ,OAASC,GAE3C,MAAMC,EAAWxB,EAASoB,EAAO,QAAQ,EACrCI,IAAa,SAAWF,EAAQ,SAAWE,GAE/C,MAAMC,EAAYvB,EAAYkB,EAAO,SAAS,EAC1CK,IAAc,SAAWH,EAAQ,UAAYG,GAE7C,OAAOL,EAAO,UAAa,YAAWE,EAAQ,SAAWF,EAAO,UAEpE,MAAMM,EAAa1B,EAASoB,EAAO,UAAU,EACzCM,IAAe,SAAWJ,EAAQ,WAAaI,GAEnD,MAAMC,EAAexB,GAAciB,EAAO,YAAY,EAClDO,IAAiB,SAAWL,EAAQ,aAAeK,GAEvD,MAAMC,EAAWtB,GAAWc,EAAO,QAAQ,EACvCQ,IAAa,SAAWN,EAAQ,SAAWM,GAE/C,MAAMpB,EAAQD,GAAQa,EAAO,KAAK,EAClC,OAAIZ,IAAU,SAAWc,EAAQ,MAAQd,GAElCc,CACT,CAGO,SAASO,GACdC,EACiE,CACjE,MAAMC,EAA6B,CAAA,EAC7BC,EAAkC,CAAA,EACxC,GAAI,CAAC,MAAM,QAAQF,CAAQ,EAAG,MAAO,CAAE,SAAAC,EAAU,OAAAC,CAAA,EAEjD,UAAWC,KAAWH,EAAU,CAE9B,GAAI,CAAC/B,EAAgBkC,CAAO,EAAG,SAC/B,GAAIA,EAAQ,YAAc,UAAW,CACnCD,EAAO,KAAKC,CAAO,EACnB,QACF,CAEA,MAAMC,EAAUrC,EAASoC,EAAQ,OAAO,EAAIA,EAAQ,QAAU,CAAA,EAGxDxC,EACJQ,EAAWiC,EAAQ,gBAAmB,GACtCjC,EAAWgC,EAAQ,QAAQ,GAC3B,UAEIX,EAAUH,EACd,CACE,KAAMe,EAAQ,KACd,QAASD,EAAQ,QACjB,KAAMC,EAAQ,KACd,SAAAzC,EACA,OAAQyC,EAAQ,OAChB,SAAUA,EAAQ,SAClB,UAAWA,EAAQ,UACnB,SAAUA,EAAQ,SAClB,WAAYA,EAAQ,WACpB,aAAcA,EAAQ,aACtB,SAAUA,EAAQ,SAClB,MAAOA,EAAQ,KAAO,EAExBH,EAAS,MAAA,EAEPT,GAASS,EAAS,KAAKT,CAAO,CACpC,CAEA,MAAO,CAAE,SAAAS,EAAU,OAAAC,CAAA,CACrB,CAGO,SAASG,EAAqBC,EAAqC,CACxE,GAAI,CAACvC,EAASuC,CAAQ,QAAU,CAAA,EAChC,MAAMC,EAAcD,EAAS,YAC7B,GAAI,CAAC,MAAM,QAAQC,CAAW,QAAU,CAAA,EAExC,MAAMN,EAA6B,CAAA,EACnC,UAAWO,KAAcD,EAAa,CACpC,GAAI,CAACxC,EAASyC,CAAU,EAAG,SAC3B,MAAMhB,EAAUH,EACd,CACE,KAAMmB,EAAW,KACjB,QAASA,EAAW,QACpB,KAAMA,EAAW,KACjB,SAAUrC,EAAWqC,EAAW,QAAW,GAAK,UAChD,OAAQA,EAAW,OACnB,SAAUA,EAAW,SACrB,UAAWA,EAAW,UACtB,SAAUA,EAAW,SACrB,WAAYA,EAAW,WACvB,aAAcA,EAAW,aACzB,SAAUA,EAAW,SACrB,MAAOA,EAAW,KAAO,EAE3BP,EAAS,MAAA,EAEPT,GAASS,EAAS,KAAKT,CAAO,CACpC,CACA,OAAOS,CACT,CAEO,SAASQ,EAAgBR,EAI9B,CACA,MAAMS,EAAS,CAAE,MAAO,EAAG,QAAS,EAAG,KAAM,CAAA,EAC7C,GAAI,CAAC,MAAM,QAAQT,CAAQ,EAAG,OAAOS,EACrC,UAAWlB,KAAWS,EAAU,CAC9B,MAAMtC,EAAWQ,EAAWqB,GAAA,YAAAA,EAAS,QAAQ,EACzC7B,IAAU+C,EAAO/C,CAAQ,GAAK,EACpC,CACA,OAAO+C,CACT,CAGO,SAASC,GAAgBC,EAAmBC,EAA2B,CAC5E,MAAMC,EAAQpD,EAAOkD,GAAA,YAAAA,EAAG,QAAQ,EAC1BG,EAAQrD,EAAOmD,GAAA,YAAAA,EAAG,QAAQ,EAChC,GAAIC,IAAUC,EAAO,OAAOD,EAAQC,EACpC,MAAMC,GAAQJ,GAAA,YAAAA,EAAG,OAAQ,GACnBK,GAAQJ,GAAA,YAAAA,EAAG,OAAQ,GACzB,GAAIG,IAAUC,EAAO,OAAOD,EAAQC,EAAQ,GAAK,EACjD,MAAMC,GAAQN,GAAA,YAAAA,EAAG,OAAQ,GACnBO,GAAQN,GAAA,YAAAA,EAAG,OAAQ,GACzB,OAAIK,IAAUC,EAAc,EACrBD,EAAQC,EAAQ,GAAK,CAC9B,CAGO,SAASC,GACdnB,EACyD,CACzD,MAAMoB,MAAa,IACnB,GAAI,CAAC,MAAM,QAAQpB,CAAQ,QAAU,CAAA,EAErC,UAAWT,KAAWS,EAAU,CAC9B,GAAI,CAACT,EAAS,SACd,MAAME,EAAWF,EAAQ,UAAY1B,EAC/BwD,EAASD,EAAO,IAAI3B,CAAQ,EAC9B4B,EAAQA,EAAO,KAAK9B,CAAO,EAC1B6B,EAAO,IAAI3B,EAAU,CAACF,CAAO,CAAC,CACrC,CAEA,MAAM+B,EAAaC,GACVA,EAAM,OAAO,CAACC,EAAOjC,IAAY,CACtC,MAAMkC,EAAOhE,EAAO8B,EAAQ,QAAQ,EACpC,OAAOkC,EAAOD,EAAQC,EAAOD,CAC/B,EAAGhE,CAAQ,EAGb,OAAO,MAAM,KAAK4D,EAAO,QAAA,CAAS,EAC/B,IAAI,CAAC,CAAC3B,EAAU4B,CAAM,KAAO,CAC5B,SAAA5B,EACA,SAAU,CAAC,GAAG4B,CAAM,EAAE,KAAKX,EAAe,CAAA,EAC1C,EACD,KAAK,CAACC,EAAGC,IAAM,CACd,MAAMc,EAAYJ,EAAUX,EAAE,QAAQ,EAAIW,EAAUV,EAAE,QAAQ,EAC9D,OAAIc,IAAc,EAAUA,EACxBf,EAAE,WAAaC,EAAE,SAAiB,EAC/BD,EAAE,SAAWC,EAAE,SAAW,GAAK,CACxC,CAAC,CACL,CAEO,SAASe,GACd3B,EACA4B,EACkB,CAClB,GAAI,CAAC,MAAM,QAAQ5B,CAAQ,QAAU,CAAA,EAErC,MAAM6B,EAAQ3D,EAAW0D,CAAG,EAAInE,EAAOmE,CAAG,EAAIpE,EAC9C,OAAOwC,EAAS,OAAQT,GAAY9B,EAAO8B,GAAA,YAAAA,EAAS,QAAQ,GAAKsC,CAAK,CACxE,CAGO,SAASC,GACdC,EACoB,CACpB,MAAMrC,EAAYvB,EAAY4D,CAAC,EAC/B,GAAKrC,EACL,OAAOA,EAAU,OAAO,CAAC,EAAE,cAAgBA,EAAU,MAAM,CAAC,CAC9D,CC1WO,MAAMsC,GAAoB;AAAA;AAAA;AAAA;AAAA,EAM3B1E,GAAa,IAAI,IAAI,CAAC,QAAS,UAAW,MAAM,CAAC,EAEvD,SAAS2E,EAAclE,EAAkD,CACvE,OAAO,OAAOA,GAAU,UAAYA,IAAU,MAAQ,CAAC,MAAM,QAAQA,CAAK,CAC5E,CASO,SAASmE,GACdC,EACqB,CACrB,MAAMC,GAAWD,GAAQ,IAAI,KAAA,EAC7B,GAAI,CAACC,EAAS,MAAO,CAAE,GAAI,GAAM,OAAQ,MAAA,EAEzC,IAAIC,EACJ,GAAI,CACFA,EAAS,KAAK,MAAMD,CAAO,CAC7B,OAASE,EAAO,CACd,MAAO,CAAE,GAAI,GAAO,MAAO,mBAAoBA,EAAgB,OAAO,EAAA,CACxE,CACA,GAAI,CAACL,EAAcI,CAAM,EACvB,MAAO,CAAE,GAAI,GAAO,MAAO,iCAAA,EAG7B,GAAI,SAAUA,EACZ,MAAO,CACL,GAAI,GACJ,MAAO,sDAAA,EAGX,GAAI,YAAaA,EACf,MAAO,CACL,GAAI,GACJ,MAAO,4DAAA,EAOX,MAAME,MAAoB,IAAI,CAC5B,QACA,eACA,iBACA,aAAA,CACD,EACKC,EAAa,OAAO,KAAKH,CAAM,EAAE,KAAMI,GAAQ,CAACF,EAAc,IAAIE,CAAG,CAAC,EAC5E,GAAID,IAAe,OACjB,MAAO,CACL,GAAI,GACJ,MAAO,gBAAgBA,CAAU,sBAAsB,CAAC,GAAGD,CAAa,EAAE,KAAK,IAAI,CAAC,GAAA,EAIxF,MAAMG,EAAQ,IAAI,IAAIC,EAAA,EAAiB,IAAKC,GAASA,EAAK,EAAE,CAAC,EACvDC,EAAgC,CAAA,EAEtC,GAAIR,EAAO,QAAU,OAAW,CAC9B,GAAI,CAACJ,EAAcI,EAAO,KAAK,EAC7B,MAAO,CACL,GAAI,GACJ,MAAO,6CAAA,EAGX,SAAW,CAAC7C,EAAQsD,CAAa,IAAK,OAAO,QAAQT,EAAO,KAAK,EAAG,CAClE,GAAI,CAACK,EAAM,IAAIlD,CAAM,EACnB,MAAO,CACL,GAAI,GACJ,MAAO,iBAAiBA,CAAM,yBAAyB,CAAC,GAAGkD,CAAK,EAAE,KAAK,IAAI,CAAC,GAAA,EAGhF,GAAI,CAACT,EAAca,CAAa,EAC9B,MAAO,CACL,GAAI,GACJ,MAAO,SAAStD,CAAM,sDAAA,EAG1B,GACEsD,EAAc,WAAa,QAC3B,CAACxF,GAAW,IAAIwF,EAAc,QAAkB,EAEhD,MAAO,CACL,GAAI,GACJ,MAAO,SAAStD,CAAM,8DAAA,EAG1B,GACEsD,EAAc,UAAY,QAC1B,OAAOA,EAAc,SAAY,UAEjC,MAAO,CACL,GAAI,GACJ,MAAO,SAAStD,CAAM,qCAAA,EAG1B,GACEsD,EAAc,aAAe,QAC7B,CAACb,EAAca,EAAc,UAAU,EAEvC,MAAO,CACL,GAAI,GACJ,MAAO,SAAStD,CAAM,oCAAA,CAG5B,CACAqD,EAAO,MAAQR,EAAO,KACxB,CAEA,GAAIA,EAAO,eAAiB,OAAW,CACrC,GAAI,CAAC,MAAM,QAAQA,EAAO,YAAY,EACpC,MAAO,CAAE,GAAI,GAAO,MAAO,kCAAA,EAE7B,SAAW,CAAClD,EAAOb,CAAK,IAAK+D,EAAO,aAAa,UAAW,CAC1D,GAAI,CAACJ,EAAc3D,CAAK,EACtB,MAAO,CAAE,GAAI,GAAO,MAAO,eAAea,CAAK,qBAAA,EAIjD,GAAI,OAAOb,EAAM,QAAW,UAAY,CAACA,EAAM,OAAO,OACpD,MAAO,CACL,GAAI,GACJ,MAAO,eAAea,CAAK,yDAAA,EAG/B,GACEb,EAAM,YAAc,QACpBA,EAAM,YAAc,SACpBA,EAAM,YAAc,UAEpB,MAAO,CACL,GAAI,GACJ,MAAO,eAAea,CAAK,6CAAA,CAGjC,CACA0D,EAAO,aAAeR,EAAO,YAC/B,CAEA,GAAIA,EAAO,iBAAmB,OAAW,CACvC,GACE,CAAC,OAAO,UAAUA,EAAO,cAAc,GACtCA,EAAO,eAA4B,EAEpC,MAAO,CACL,GAAI,GACJ,MAAO,wDAAA,EAGXQ,EAAO,eAAiBR,EAAO,cACjC,CAEA,GAAIA,EAAO,cAAgB,OAAW,CACpC,GAAIA,EAAO,cAAgB,YAAcA,EAAO,cAAgB,QAC9D,MAAO,CACL,GAAI,GACJ,MAAO,8CAAA,EAGXQ,EAAO,YAAcR,EAAO,WAC9B,CAQA,MAAO,CAAE,GAAI,GAAM,QALhBQ,EAAO,QAAU,QAAa,OAAO,KAAKA,EAAO,KAAK,EAAE,SAAW,KACnEA,EAAO,eAAiB,QAAaA,EAAO,aAAa,SAAW,IACrEA,EAAO,iBAAmB,QAC1BA,EAAO,cAAgB,OAEU,OAAYA,CAAA,CACjD,CCpMO,MAAME,GAGT,CACF,KAAM,CACJ,CACE,GAAI,mBACJ,MAAO,mBACP,YAAa,wDAAA,EAEf,CACE,GAAI,mBACJ,MAAO,mBACP,YAAa,uCAAA,EAEf,CACE,GAAI,iBACJ,MAAO,iBACP,YAAa,4DAAA,CACf,EAEF,KAAM,CACJ,CACE,GAAI,yBACJ,MAAO,yBACP,YAAa,uDAAA,EAEf,CACE,GAAI,yBACJ,MAAO,yBACP,YAAa,6CAAA,CACf,CAEJ,EAQO,SAASC,GACdC,EACoB,CACpB,MAAMC,EAAKD,GAAA,YAAAA,EAAME,GACjB,OAAOC,EAAmBF,CAAE,EAAIA,EAAK,MACvC,CAGO,SAASE,EAAmBF,EAAsC,CACvE,OAAOA,IAAO,QAAaH,GAAiBI,CAAM,EAAE,KAAME,GAAMA,EAAE,KAAOH,CAAE,CAC7E,CAcO,SAASI,GACdC,EACAC,EACAC,EACmC,CACnC,MAAMC,EAAiC,CAAA,EACnCN,EAAmBG,CAAS,IAC9BG,EAAQ,QAAU,CAAE,GAAIH,EAAW,QAAS,CAACJ,CAAM,CAAA,GAIrD,MAAMd,EAASH,GAAmBuB,CAAU,EACtCE,EAAWtB,EAAO,GAAKA,EAAO,OAAS,OACvCuB,EAAUJ,IAAS,SAAWA,IAAS,WAAaA,IAAS,OACnE,OAAIG,GAAYC,KACdF,EAAQ,OAAS,CACf,GAAIC,GAAY,CAAA,EAGhB,GAAIC,GAAW,CAAE,KAAAJ,CAAA,CAAK,GAGnBE,EAAQ,SAAWA,EAAQ,OAASA,EAAU,MACvD,CC7FA,IAAIG,EAAU,EAGP,SAASC,IAA4B,CAC1C,OAAAD,GAAW,EACJA,CACT,CAGO,SAASE,GACdC,EACAC,EACS,CACT,OAAOD,GAAW,MAAQA,EAAQ,IAAMC,CAC1C,CCNA,MAAMC,GAAuB,KAa7B,eAAeC,GACbC,EAC2C,CAI3C,GAAKC,EAAgBD,EAAS,QAAQ,IAAI,cAAc,CAAC,EACzD,GAAI,CACF,OAAQ,MAAMA,EAAS,KAAA,CACzB,MAAQ,CACN,MACF,CACF,CASA,SAASE,GACPjE,EAC2D,CAC3D,GAAI,CAACA,GAAY,OAAOA,GAAa,eAAiB,CAAA,EACtD,MAAMkE,EAASlE,EACf,MAAO,CACL,GAAI,OAAOkE,EAAO,WAAc,SAC5B,CAAE,UAAWA,EAAO,SAAA,EACpB,CAAA,EACJ,GAAI,OAAOA,EAAO,SAAY,UAAY,CAAE,QAASA,EAAO,OAAA,EAAY,CAAA,EACxE,GAAI,OAAOA,EAAO,WAAc,UAC5B,CAAE,UAAWA,EAAO,WACpB,CAAA,CAAC,CAET,CAGA,SAASC,GACPJ,EACAK,EACQ,CACR,OAAIA,GAAA,MAAAA,EAAM,MAAcA,EAAK,MACtB,4BAA4BL,EAAS,MAAM,GAChDA,EAAS,WAAa,IAAIA,EAAS,UAAU,GAAK,EACpD,IACF,CASO,SAASM,IAOd,CACA,MAAMC,EAAYC,EAAgBC,GAAUA,EAAM,SAAS,EACrDC,EAAcC,EAAAA,WAAWC,CAAkB,EAI3CC,EAAaC,EAAkBL,GAAUA,EAAM,iBAAiB,EAChEtB,EAAYP,GAAgBiC,CAAU,EACtCzB,EAAO0B,EAAkBL,GAAUA,EAAM,WAAW,EACpDpB,EAAayB,EAChBL,GAAA,OAAU,OAAAM,EAAAN,EAAM,kBAAN,YAAAM,EAAwBhC,GAAM,EAGrCiC,EAAWC,EAAAA,OAA6C,IAAI,EAC5DC,EAAWD,EAAAA,OAA+B,IAAI,EAE9CE,EAASC,EAAAA,YAAY,IAAM,OAC3BJ,EAAS,UAAY,OACvB,aAAaA,EAAS,OAAO,EAC7BA,EAAS,QAAU,OAErBD,EAAAG,EAAS,UAAT,MAAAH,EAAkB,QAClBG,EAAS,QAAU,IACrB,EAAG,CAAA,CAAE,EAKLG,EAAAA,UAAU,IAAMF,EAAQ,CAACA,CAAM,CAAC,EAShC,MAAMG,EAASF,EAAAA,YACb,CAACvB,EAAgB0B,IAAuB,CAClC5B,GAAqBe,EAAY,SAAA,EAAW,QAASb,CAAM,GAC/DU,EAAU,CAAE,QAASgB,EAAM,CAC7B,EACA,CAACb,EAAaH,CAAS,CAAA,EAGnBiB,EAAMJ,EAAAA,YACV,MAAOK,EAAsBC,IAAyB,SACpD,IAAIC,EACJ,GAAI,CACFA,EAAiB,KAAK,MAAMD,CAAY,CAC1C,MAAQ,CAKN,MACF,EAEAX,EAAAG,EAAS,UAAT,MAAAH,EAAkB,QAClB,MAAMa,EAAa,IAAI,gBACvBV,EAAS,QAAUU,EACnB,MAAM/B,EAASH,GAAA,EAETmC,EAAU3C,GAAoBC,EAAWC,EAAMC,CAAU,EAE/D,GAAI,CACF,MAAMW,EAAW,MAAM,MAAM8B,EAAc,SAAU,CACnD,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAA,EAC3B,KAAM,KAAK,UAAU,CACnB,eAAAH,EACA,QAASE,EAAU,CAAE,QAAAA,GAAY,CAAA,CAAC,CACnC,EACD,OAAQD,EAAW,MAAA,CACpB,EACKvB,EAAO,MAAMN,GAAaC,CAAQ,EAIxC,GAAI4B,EAAW,OAAO,QAAS,OAE/B,GAAI,CAAC5B,EAAS,GAAI,CAChB,MAAMrF,EAAUyF,GAAgBJ,EAAUK,CAAI,EAC9C,IAAIA,GAAA,YAAAA,EAAM,QAAS,sBAAuB,CAExC,MAAMzE,EAAWI,EAAqBqE,EAAK,OAAO,EAClDiB,EAAOzB,EAAQ,CACb,SAAAjE,EACA,OAAQQ,EAAgBR,CAAQ,EAChC,aAAA6F,EACA,IAAK5B,EACL,QAAS,GACT,OAAQ,WACR,WAAY,KAAK,IAAA,EACjB,UAAWlF,CAAA,CACZ,EACD,MACF,CAYA,MAAMoH,EAASrB,EAAY,SAAA,EAAW,QAChCsB,EACJD,GAAUA,EAAO,eAAiBN,EAAeM,EAAS,OAC5DT,EAAOzB,EAAQ,CACb,GAAImC,GAAY,CACd,SAAU,CAAA,EACV,OAAQ,CAAE,MAAO,EAAG,QAAS,EAAG,KAAM,CAAA,EACtC,OAAQ,UAAA,EAEV,aAAAP,EACA,IAAK5B,EACL,WAAY,KAAK,IAAA,EACjB,SAASmC,GAAA,YAAAA,EAAU,UAAW,GAC9B,UAAW,OACX,cAAerH,CAAA,CAChB,EACD,MACF,CAKA,MAAMsB,GAAWgG,EAAA5B,GAAA,YAAAA,EAAM,OAAN,YAAA4B,EAAY,gBACvBrG,EAAWI,EAAqBC,CAAQ,EAC9CqF,EAAOzB,EAAQ,CACb,SAAAjE,EACA,OAAQQ,EAAgBR,CAAQ,EAChC,GAAGsE,GAAkBjE,CAAQ,EAC7B,aAAAwF,EACA,IAAK5B,EACL,OAAQ,WACR,WAAY,KAAK,IAAA,CAAI,CACtB,CACH,MAAQ,CAGR,QAAA,CACMqB,EAAS,UAAYU,IAAYV,EAAS,QAAU,KAC1D,CACF,EACA,CAACI,EAAQlC,EAAMsB,EAAarB,EAAYF,CAAS,CAAA,EAyBnD,MAAO,CAAE,QAtBOiC,EAAAA,YACd,CACEK,EACAC,EACApC,IACG,CAKH,GAJI0B,EAAS,UAAY,OACvB,aAAaA,EAAS,OAAO,EAC7BA,EAAS,QAAU,MAEjB1B,GAAA,MAAAA,EAAS,UAAW,CACjBkC,EAAIC,EAAcC,CAAY,EACnC,MACF,CACAV,EAAS,QAAU,WAAW,IAAM,CAClCA,EAAS,QAAU,KACdQ,EAAIC,EAAcC,CAAY,CACrC,EAAG5B,EAAoB,CACzB,EACA,CAAC0B,CAAG,CAAA,EAGY,OAAAL,CAAA,CACpB"}
1
+ {"version":3,"file":"useQualityAnalysis-BFG1bJDp.js","sources":["../../../src/client/lib/quality-findings.ts","../../../src/client/lib/quality-policy.ts","../../../src/client/lib/quality-profiles.ts","../../../src/client/lib/quality-sequence.ts","../../../src/client/hooks/useQualityAnalysis.ts"],"sourcesContent":["/**\n * The playground's own model of a quality finding.\n *\n * The server hands the same finding over in two shapes: `/validate` returns\n * rich `QualityDiagnostic` objects, while `/generate` flattens each one into a\n * `GenerationWarning` whose real fields live in `context` and whose severity is\n * squashed to `warning | info`. Normalising both here keeps that asymmetry out\n * of the panel, the store and the Monaco markers.\n */\n\nexport type QualitySeverity = 'error' | 'warning' | 'info';\n\nexport type QualityCertainty =\n | 'deterministic'\n | 'measured'\n | 'estimated'\n | 'rendered'\n | 'evaluative';\n\nexport interface QualityFixOp {\n op: 'add' | 'replace' | 'remove' | 'move' | 'copy' | 'test';\n path: string;\n value?: unknown;\n from?: string;\n}\n\nexport interface QualityEvidence {\n actual?: unknown;\n expected?: unknown;\n unit?: string;\n [key: string]: unknown;\n}\n\nexport interface QualityFinding {\n /** Stable within one analysis; used as a React key and for fix dedup. */\n id: string;\n code: string;\n message: string;\n /** RFC 6901 pointer into the authored document. */\n path: string;\n severity: QualitySeverity;\n ruleId?: string;\n category?: string;\n certainty?: QualityCertainty;\n blocking?: boolean;\n suggestion?: string;\n relatedPaths?: readonly string[];\n evidence?: QualityEvidence;\n fixes?: readonly QualityFixOp[];\n}\n\nexport interface GenerationWarningLike {\n component: string;\n message: string;\n severity?: 'warning' | 'info';\n context?: Record<string, unknown>;\n}\n\nconst SEVERITIES: readonly QualitySeverity[] = ['error', 'warning', 'info'];\n\nconst SEVERITY_RANK: Record<string, number> = {\n error: 0,\n warning: 1,\n info: 2,\n};\n\n/**\n * Anything that is not one of the three legal severities sorts last rather\n * than throwing, so a future server-side severity cannot crash the panel.\n */\nconst UNRANKED = 3;\n\nfunction rankOf(severity: unknown): number {\n return typeof severity === 'string' && severity in SEVERITY_RANK\n ? (SEVERITY_RANK[severity] as number)\n : UNRANKED;\n}\n\nconst CERTAINTIES: readonly QualityCertainty[] = [\n 'deterministic',\n 'measured',\n 'estimated',\n 'rendered',\n 'evaluative',\n];\n\nconst FIX_OPS: readonly QualityFixOp['op'][] = [\n 'add',\n 'replace',\n 'remove',\n 'move',\n 'copy',\n 'test',\n];\n\n/** Findings with no category are grouped under this bucket. */\nconst UNCATEGORIZED = 'other';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The same test as `isRecord` but without the type predicate, for values that\n * are already typed and only need the runtime guard — narrowing them to\n * `Record<string, unknown>` would throw away the declared shape.\n */\nfunction looksLikeObject(value: unknown): boolean {\n return isRecord(value);\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction asSeverity(value: unknown): QualitySeverity | undefined {\n return typeof value === 'string' &&\n (SEVERITIES as readonly string[]).includes(value)\n ? (value as QualitySeverity)\n : undefined;\n}\n\nfunction asCertainty(value: unknown): QualityCertainty | undefined {\n return typeof value === 'string' &&\n (CERTAINTIES as readonly string[]).includes(value)\n ? (value as QualityCertainty)\n : undefined;\n}\n\nfunction asStringArray(value: unknown): readonly string[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const strings = value.filter((entry): entry is string => {\n return typeof entry === 'string';\n });\n return strings.length > 0 ? strings : undefined;\n}\n\nfunction asEvidence(value: unknown): QualityEvidence | undefined {\n return isRecord(value) ? (value as QualityEvidence) : undefined;\n}\n\n/**\n * A malformed patch operation is worse than none: the fix button would apply\n * garbage to the author's document, so anything without a legal `op` and a\n * string `path` is discarded rather than passed along.\n */\nfunction asFixes(value: unknown): readonly QualityFixOp[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const fixes: QualityFixOp[] = [];\n for (const entry of value) {\n if (!isRecord(entry)) continue;\n const op = entry['op'];\n const path = entry['path'];\n if (typeof op !== 'string' || typeof path !== 'string') continue;\n if (!(FIX_OPS as readonly string[]).includes(op)) continue;\n const fix: QualityFixOp = { op: op as QualityFixOp['op'], path };\n if ('value' in entry) fix.value = entry['value'];\n const from = asString(entry['from']);\n if (from !== undefined) fix.from = from;\n fixes.push(fix);\n }\n return fixes.length > 0 ? fixes : undefined;\n}\n\n/**\n * The generate path double-encodes the code: the message arrives as\n * \"[W_QUALITY_FONT_SIZE_MIN] Body text is 5pt\" while the code is also in\n * `context.code`. Stripping the prefix keeps the panel from showing the code\n * twice, but only when the two agree — an unrelated bracketed prefix is part\n * of the rule's own wording and must survive.\n */\nfunction stripCodePrefix(message: string, code: string | undefined): string {\n if (!code) return message;\n const prefix = `[${code}] `;\n return message.startsWith(prefix) ? message.slice(prefix.length) : message;\n}\n\n/**\n * Ids must survive a re-render of the same analysis, so they are derived from\n * the finding rather than from a counter or a random value. The index breaks\n * ties between findings that share a code and a path.\n */\nfunction makeId(code: string, path: string, index: number): string {\n return `${code || 'quality'}:${path}:${index}`;\n}\n\nfunction buildFinding(\n fields: {\n code: unknown;\n message: unknown;\n path: unknown;\n severity: QualitySeverity;\n ruleId: unknown;\n category: unknown;\n certainty: unknown;\n blocking: unknown;\n suggestion: unknown;\n relatedPaths: unknown;\n evidence: unknown;\n fixes: unknown;\n },\n index: number\n): QualityFinding | undefined {\n const code = asString(fields.code) ?? '';\n const rawMessage = asString(fields.message) ?? '';\n // Nothing to render and nothing to look up: the entry is not a finding.\n if (!code && !rawMessage) return undefined;\n\n const path = asString(fields.path) ?? '';\n const finding: QualityFinding = {\n id: makeId(code, path, index),\n code,\n // A finding row with no text at all is unreadable, so a code-only entry\n // shows its code rather than rendering blank.\n message: stripCodePrefix(rawMessage, code) || code,\n path,\n severity: fields.severity,\n };\n\n const ruleId = asString(fields.ruleId);\n if (ruleId !== undefined) finding.ruleId = ruleId;\n\n const category = asString(fields.category);\n if (category !== undefined) finding.category = category;\n\n const certainty = asCertainty(fields.certainty);\n if (certainty !== undefined) finding.certainty = certainty;\n\n if (typeof fields.blocking === 'boolean') finding.blocking = fields.blocking;\n\n const suggestion = asString(fields.suggestion);\n if (suggestion !== undefined) finding.suggestion = suggestion;\n\n const relatedPaths = asStringArray(fields.relatedPaths);\n if (relatedPaths !== undefined) finding.relatedPaths = relatedPaths;\n\n const evidence = asEvidence(fields.evidence);\n if (evidence !== undefined) finding.evidence = evidence;\n\n const fixes = asFixes(fields.fixes);\n if (fixes !== undefined) finding.fixes = fixes;\n\n return finding;\n}\n\n/** Split a generate response's warnings into quality findings and everything else. */\nexport function splitQualityWarnings(\n warnings: readonly GenerationWarningLike[] | null | undefined\n): { findings: QualityFinding[]; others: GenerationWarningLike[] } {\n const findings: QualityFinding[] = [];\n const others: GenerationWarningLike[] = [];\n if (!Array.isArray(warnings)) return { findings, others };\n\n for (const warning of warnings) {\n // The array is typed, but it comes off the wire — a null slot is possible.\n if (!looksLikeObject(warning)) continue;\n if (warning.component !== 'quality') {\n others.push(warning);\n continue;\n }\n\n const context = isRecord(warning.context) ? warning.context : {};\n // `context.originalSeverity` is the only place an 'error' survives; the\n // top-level severity was already coerced to warning | info by the server.\n const severity =\n asSeverity(context['originalSeverity']) ??\n asSeverity(warning.severity) ??\n 'warning';\n\n const finding = buildFinding(\n {\n code: context['code'],\n message: warning.message,\n path: context['path'],\n severity,\n ruleId: context['ruleId'],\n category: context['category'],\n certainty: context['certainty'],\n blocking: context['blocking'],\n suggestion: context['suggestion'],\n relatedPaths: context['relatedPaths'],\n evidence: context['evidence'],\n fixes: context['fixes'],\n },\n findings.length\n );\n if (finding) findings.push(finding);\n }\n\n return { findings, others };\n}\n\n/** Findings from a /validate response's qualityAnalysis object. */\nexport function findingsFromAnalysis(analysis: unknown): QualityFinding[] {\n if (!isRecord(analysis)) return [];\n const diagnostics = analysis['diagnostics'];\n if (!Array.isArray(diagnostics)) return [];\n\n const findings: QualityFinding[] = [];\n for (const diagnostic of diagnostics) {\n if (!isRecord(diagnostic)) continue;\n const finding = buildFinding(\n {\n code: diagnostic['code'],\n message: diagnostic['message'],\n path: diagnostic['path'],\n severity: asSeverity(diagnostic['severity']) ?? 'warning',\n ruleId: diagnostic['ruleId'],\n category: diagnostic['category'],\n certainty: diagnostic['certainty'],\n blocking: diagnostic['blocking'],\n suggestion: diagnostic['suggestion'],\n relatedPaths: diagnostic['relatedPaths'],\n evidence: diagnostic['evidence'],\n fixes: diagnostic['fixes'],\n },\n findings.length\n );\n if (finding) findings.push(finding);\n }\n return findings;\n}\n\nexport function countBySeverity(findings: readonly QualityFinding[]): {\n error: number;\n warning: number;\n info: number;\n} {\n const counts = { error: 0, warning: 0, info: 0 };\n if (!Array.isArray(findings)) return counts;\n for (const finding of findings) {\n const severity = asSeverity(finding?.severity);\n if (severity) counts[severity] += 1;\n }\n return counts;\n}\n\n/** error < warning < info, then by path, then by code. For Array.sort. */\nexport function compareFindings(a: QualityFinding, b: QualityFinding): number {\n const rankA = rankOf(a?.severity);\n const rankB = rankOf(b?.severity);\n if (rankA !== rankB) return rankA - rankB;\n const pathA = a?.path ?? '';\n const pathB = b?.path ?? '';\n if (pathA !== pathB) return pathA < pathB ? -1 : 1;\n const codeA = a?.code ?? '';\n const codeB = b?.code ?? '';\n if (codeA === codeB) return 0;\n return codeA < codeB ? -1 : 1;\n}\n\n/** Groups sorted by worst severity within the group, then category name. */\nexport function groupByCategory(\n findings: readonly QualityFinding[]\n): Array<{ category: string; findings: QualityFinding[] }> {\n const groups = new Map<string, QualityFinding[]>();\n if (!Array.isArray(findings)) return [];\n\n for (const finding of findings) {\n if (!finding) continue;\n const category = finding.category || UNCATEGORIZED;\n const bucket = groups.get(category);\n if (bucket) bucket.push(finding);\n else groups.set(category, [finding]);\n }\n\n const worstRank = (group: readonly QualityFinding[]): number => {\n return group.reduce((worst, finding) => {\n const rank = rankOf(finding.severity);\n return rank < worst ? rank : worst;\n }, UNRANKED);\n };\n\n return Array.from(groups.entries())\n .map(([category, bucket]) => ({\n category,\n findings: [...bucket].sort(compareFindings),\n }))\n .sort((a, b) => {\n const rankDelta = worstRank(a.findings) - worstRank(b.findings);\n if (rankDelta !== 0) return rankDelta;\n if (a.category === b.category) return 0;\n return a.category < b.category ? -1 : 1;\n });\n}\n\nexport function filterByMinSeverity(\n findings: readonly QualityFinding[],\n min: QualitySeverity\n): QualityFinding[] {\n if (!Array.isArray(findings)) return [];\n // An unknown threshold must not silently hide findings, so it lets all pass.\n const limit = asSeverity(min) ? rankOf(min) : UNRANKED;\n return findings.filter((finding) => rankOf(finding?.severity) <= limit);\n}\n\n/** Human label for a certainty, e.g. 'measured' -> 'Measured'. */\nexport function certaintyLabel(\n c: QualityCertainty | undefined\n): string | undefined {\n const certainty = asCertainty(c);\n if (!certainty) return undefined;\n return certainty.charAt(0).toUpperCase() + certainty.slice(1);\n}\n","/**\n * The run policy an author writes by hand.\n *\n * The profile and gate controls cover the two settings most runs need; this\n * covers the rest of the contract — per-rule severity, enable/disable and\n * parameters, suppressions, and the diagnostic budget — which until now was\n * reachable only through the CLI's `--quality-policy` file.\n *\n * `gate` is deliberately NOT accepted here. It has a control of its own, and\n * two writable sources for one field means the dropdown either lies or silently\n * loses, so naming it in the JSON is reported as a mistake instead.\n */\n\nimport { rulesForFormat } from './quality-rules';\n\nexport interface QualityRuleConfiguration {\n enabled?: boolean;\n severity?: 'error' | 'warning' | 'info';\n parameters?: Record<string, unknown>;\n}\n\nexport interface QualitySuppression {\n ruleId?: string;\n code?: string;\n path?: string;\n pathMatch?: 'exact' | 'subtree';\n reason: string;\n}\n\nexport interface EditableQualityPolicy {\n rules?: Record<string, QualityRuleConfiguration>;\n suppressions?: QualitySuppression[];\n maxDiagnostics?: number;\n onRuleError?: 'continue' | 'throw';\n}\n\nexport type ParsedQualityPolicy =\n | { ok: true; policy: EditableQualityPolicy | undefined }\n | { ok: false; error: string };\n\n/** What a fresh editor opens on: valid, inert, and a shape to copy. */\nexport const EMPTY_POLICY_TEXT = `{\n \"rules\": {},\n \"suppressions\": []\n}\n`;\n\nconst SEVERITIES = new Set(['error', 'warning', 'info']);\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parse and check a hand-written policy.\n *\n * Errors are values and name the offending key, because this text is typed by\n * hand and the server's own rejection would arrive as a 400 the panel reports\n * far from the field that caused it.\n */\nexport function parseQualityPolicy(\n text: string | undefined\n): ParsedQualityPolicy {\n const trimmed = (text ?? '').trim();\n if (!trimmed) return { ok: true, policy: undefined };\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch (error) {\n return { ok: false, error: `Not valid JSON: ${(error as Error).message}` };\n }\n if (!isPlainObject(parsed)) {\n return { ok: false, error: 'A policy must be a JSON object.' };\n }\n\n if ('gate' in parsed) {\n return {\n ok: false,\n error: 'Remove \"gate\" — it is set by the Gate control above.',\n };\n }\n if ('profile' in parsed) {\n return {\n ok: false,\n error: 'Remove \"profile\" — it is set by the Profile control above.',\n };\n }\n\n // Every key below is read by name, so anything else would be dropped in\n // silence — `{\"maxDiagnostic\": 0}` parsed as a valid empty policy and the\n // budget the author asked for never applied. Say so instead.\n const EDITABLE_KEYS = new Set([\n 'rules',\n 'suppressions',\n 'maxDiagnostics',\n 'onRuleError',\n ]);\n const unknownKey = Object.keys(parsed).find((key) => !EDITABLE_KEYS.has(key));\n if (unknownKey !== undefined) {\n return {\n ok: false,\n error: `Unknown key \"${unknownKey}\". A policy takes: ${[...EDITABLE_KEYS].join(', ')}.`,\n };\n }\n\n const known = new Set(rulesForFormat().map((rule) => rule.id));\n const policy: EditableQualityPolicy = {};\n\n if (parsed.rules !== undefined) {\n if (!isPlainObject(parsed.rules)) {\n return {\n ok: false,\n error: '\"rules\" must be an object keyed by rule id.',\n };\n }\n for (const [ruleId, configuration] of Object.entries(parsed.rules)) {\n if (!known.has(ruleId)) {\n return {\n ok: false,\n error: `Unknown rule \"${ruleId}\". This format ships: ${[...known].join(', ')}.`,\n };\n }\n if (!isPlainObject(configuration)) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\" must be an object, e.g. { \"severity\": \"warning\" }.`,\n };\n }\n if (\n configuration.severity !== undefined &&\n !SEVERITIES.has(configuration.severity as string)\n ) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\" has an invalid severity. Use \"error\", \"warning\" or \"info\".`,\n };\n }\n if (\n configuration.enabled !== undefined &&\n typeof configuration.enabled !== 'boolean'\n ) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\": \"enabled\" must be true or false.`,\n };\n }\n if (\n configuration.parameters !== undefined &&\n !isPlainObject(configuration.parameters)\n ) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\": \"parameters\" must be an object.`,\n };\n }\n }\n policy.rules = parsed.rules as Record<string, QualityRuleConfiguration>;\n }\n\n if (parsed.suppressions !== undefined) {\n if (!Array.isArray(parsed.suppressions)) {\n return { ok: false, error: '\"suppressions\" must be an array.' };\n }\n for (const [index, entry] of parsed.suppressions.entries()) {\n if (!isPlainObject(entry)) {\n return { ok: false, error: `Suppression ${index} must be an object.` };\n }\n // The contract makes `reason` mandatory on purpose: a muted finding that\n // nobody has to justify is how a rule quietly stops being enforced.\n if (typeof entry.reason !== 'string' || !entry.reason.trim()) {\n return {\n ok: false,\n error: `Suppression ${index} needs a \"reason\" saying why the finding is acceptable.`,\n };\n }\n if (\n entry.pathMatch !== undefined &&\n entry.pathMatch !== 'exact' &&\n entry.pathMatch !== 'subtree'\n ) {\n return {\n ok: false,\n error: `Suppression ${index}: \"pathMatch\" must be \"exact\" or \"subtree\".`,\n };\n }\n }\n policy.suppressions = parsed.suppressions as QualitySuppression[];\n }\n\n if (parsed.maxDiagnostics !== undefined) {\n if (\n !Number.isInteger(parsed.maxDiagnostics) ||\n (parsed.maxDiagnostics as number) < 0\n ) {\n return {\n ok: false,\n error: '\"maxDiagnostics\" must be a whole number, zero or more.',\n };\n }\n policy.maxDiagnostics = parsed.maxDiagnostics as number;\n }\n\n if (parsed.onRuleError !== undefined) {\n if (parsed.onRuleError !== 'continue' && parsed.onRuleError !== 'throw') {\n return {\n ok: false,\n error: '\"onRuleError\" must be \"continue\" or \"throw\".',\n };\n }\n policy.onRuleError = parsed.onRuleError;\n }\n\n const empty =\n (policy.rules === undefined || Object.keys(policy.rules).length === 0) &&\n (policy.suppressions === undefined || policy.suppressions.length === 0) &&\n policy.maxDiagnostics === undefined &&\n policy.onRuleError === undefined;\n\n return { ok: true, policy: empty ? undefined : policy };\n}\n","/**\n * The shipped quality profiles, and the one place a stored profile id is\n * turned into a request.\n *\n * Settings persist to a single localStorage key shared by both playgrounds\n * (`window.__JTO_FORMAT__` picks the format; the origin is the same), so a\n * profile chosen on the DOCX playground is still in storage when the PPTX one\n * opens. Sending it would be worse than useless: the client stamps\n * `formats: [FORMAT]` itself, so the server's compatibility check passes, the\n * id matches no registered profile, and the analysis silently runs on defaults\n * while the panel and the response both report the profile as active.\n * Resolution therefore happens here, at the point of use, rather than in a\n * component that only guards while it is mounted.\n */\n\nimport { FORMAT, type FormatName } from './env';\nimport { parseQualityPolicy } from './quality-policy';\n\nexport interface QualityProfileChoice {\n id: string;\n label: string;\n description: string;\n}\n\nexport const QUALITY_PROFILES: Record<\n FormatName,\n readonly QualityProfileChoice[]\n> = {\n docx: [\n {\n id: 'executive-report',\n label: 'Executive report',\n description: 'Short decision document with strict outline continuity',\n },\n {\n id: 'technical-report',\n label: 'Technical report',\n description: 'Portable professional report defaults',\n },\n {\n id: 'legal-appendix',\n label: 'Legal appendix',\n description: 'Dense appendix: preserve integrity without editorial taste',\n },\n ],\n pptx: [\n {\n id: 'executive-presentation',\n label: 'Executive presentation',\n description: 'Decision deck optimized for scan speed and projection',\n },\n {\n id: 'technical-presentation',\n label: 'Technical presentation',\n description: 'Portable professional presentation defaults',\n },\n ],\n};\n\n/** The profiles offerable for the format this playground is serving. */\nexport function profilesForFormat(): readonly QualityProfileChoice[] {\n return QUALITY_PROFILES[FORMAT];\n}\n\n/** The profile id stored for this format, if it still names a shipped one. */\nexport function storedProfileId(\n ids: Partial<Record<FormatName, string>> | undefined\n): string | undefined {\n const id = ids?.[FORMAT];\n return isProfileForFormat(id) ? id : undefined;\n}\n\n/** True when `id` names a profile the current format actually ships. */\nexport function isProfileForFormat(id: string | undefined): id is string {\n return id !== undefined && QUALITY_PROFILES[FORMAT].some((p) => p.id === id);\n}\n\nexport interface QualityRequestOptions {\n profile?: { id: string; formats: string[] };\n policy?: Record<string, unknown>;\n}\n\n/**\n * Build the `options.quality` payload for a generate or validate request.\n *\n * Returns undefined when neither a profile nor a gate was asked for, so the\n * request omits the key entirely and the server keeps its own defaults. A\n * profile id belonging to the other format is dropped rather than sent.\n */\nexport function buildQualityOptions(\n profileId: string | undefined,\n gate: string | undefined,\n policyText?: string\n): QualityRequestOptions | undefined {\n const options: QualityRequestOptions = {};\n if (isProfileForFormat(profileId)) {\n options.profile = { id: profileId, formats: [FORMAT] };\n }\n // A policy still being typed is not sent at all: the run keeps working under\n // the last good one rather than failing on every unbalanced brace.\n const parsed = parseQualityPolicy(policyText);\n const authored = parsed.ok ? parsed.policy : undefined;\n const hasGate = gate === 'error' || gate === 'warning' || gate === 'info';\n if (authored || hasGate) {\n options.policy = {\n ...(authored ?? {}),\n // The Gate control owns this field; `parseQualityPolicy` rejects a policy\n // that also names it, so there is nothing here to overwrite.\n ...(hasGate && { gate }),\n };\n }\n return options.profile || options.policy ? options : undefined;\n}\n","/**\n * Ordering tickets for quality analyses.\n *\n * Three independent producers write the same store slice: the editor's\n * debounced validate pass, the preview's immediate re-analysis after a fix,\n * and the build's own findings. Each aborts only its own in-flight request, so\n * per-request cancellation cannot order them against each other — a ~10s build\n * started before an edit still resolves after the validate pass that overtook\n * it, and would otherwise reinstate findings for text the editor no longer\n * holds.\n *\n * Every producer takes a ticket before it starts and drops its result if a\n * higher one has already landed. Tickets are process-wide because the writers\n * are separate hook instances in one bundle; a per-instance counter would\n * compare numbers that mean nothing to each other.\n */\n\nimport type { QualityState } from '../store/output-store';\n\nlet counter = 0;\n\n/** Monotonic, allocated at the moment work starts, not when it finishes. */\nexport function nextQualityTicket(): number {\n counter += 1;\n return counter;\n}\n\n/** True when a newer analysis has already been committed. */\nexport function isStaleQualityTicket(\n current: QualityState | null | undefined,\n ticket: number\n): boolean {\n return current != null && current.seq > ticket;\n}\n","import { useCallback, useContext, useEffect, useRef } from 'react';\nimport { API_ENDPOINTS } from '../config/api';\nimport { FORMAT } from '../lib/env';\nimport { countBySeverity, findingsFromAnalysis } from '../lib/quality-findings';\nimport { buildQualityOptions, storedProfileId } from '../lib/quality-profiles';\nimport {\n isStaleQualityTicket,\n nextQualityTicket,\n} from '../lib/quality-sequence';\nimport type { QualityState } from '../store/output-store';\nimport {\n OutputStoreContext,\n useOutputStore,\n} from '../store/output-store-provider';\nimport { useSettingsStore } from '../store/settings-store-provider';\nimport { isJsonMediaType } from './usePresentationGenerator';\n\n/**\n * Long enough that a burst of typing costs one request rather than one per\n * keystroke, short enough that a pause reads as \"the panel just updated\".\n *\n * The upper bound is not taste: `/api/*` rate-limits on `METHOD:path`, and in\n * production that budget is 100 requests per 15 minutes (server config\n * `RATE_LIMIT_MAX`). One analysis per typing pause has to stay well under\n * ~7 requests/minute sustained or the panel starts answering 429 to the author\n * who is typing the most.\n */\nconst ANALYSIS_DEBOUNCE_MS = 1500;\n\ninterface ValidateResponseBody {\n success?: boolean;\n data?: { valid?: boolean; qualityAnalysis?: unknown };\n error?: string;\n /** `QUALITY_GATE_FAILED` distinguishes a real rejection from a 429 or a 500. */\n code?: string;\n /** Present on a gate failure: the analysis that tripped it. */\n quality?: unknown;\n}\n\n/** The JSON body, or undefined when the response is not JSON at all. */\nasync function readJsonBody(\n response: Response\n): Promise<ValidateResponseBody | undefined> {\n // A proxy that never reached the API answers with its own HTML error page,\n // and parsing that unguarded is what turns a dead backend into\n // `Unexpected token '<'` instead of the status it actually was.\n if (!isJsonMediaType(response.headers.get('content-type'))) return undefined;\n try {\n return (await response.json()) as ValidateResponseBody;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The analysis-level fields, read one at a time.\n *\n * Spreading the server's object into the store would let an unexpected type\n * through to the panel, where a `blocked` that is a string reads as truthy and\n * a `profileId` that is an object renders as `[object Object]`.\n */\nfunction readAnalysisFlags(\n analysis: unknown\n): Pick<QualityState, 'profileId' | 'blocked' | 'truncated'> {\n if (!analysis || typeof analysis !== 'object') return {};\n const source = analysis as Record<string, unknown>;\n return {\n ...(typeof source.profileId === 'string'\n ? { profileId: source.profileId }\n : {}),\n ...(typeof source.blocked === 'boolean' ? { blocked: source.blocked } : {}),\n ...(typeof source.truncated === 'boolean'\n ? { truncated: source.truncated }\n : {}),\n };\n}\n\n/** The API's own message when there is one; the status is all that is left. */\nfunction describeFailure(\n response: Response,\n body: ValidateResponseBody | undefined\n): string {\n if (body?.error) return body.error;\n return `Quality analysis failed (${response.status}${\n response.statusText ? ` ${response.statusText}` : ''\n }).`;\n}\n\n/**\n * Re-run the quality analysis as the document changes, without rendering it.\n *\n * `/validate` returns the rich diagnostics `/generate` flattens into warnings,\n * so this is what keeps the panel current between builds — the author sees the\n * findings move while editing rather than only after asking for bytes.\n */\nexport function useQualityAnalysis(): {\n analyze: (\n documentName: string,\n documentText: string,\n options?: { immediate?: boolean }\n ) => void;\n cancel: () => void;\n} {\n const setOutput = useOutputStore((state) => state.setOutput);\n const outputStore = useContext(OutputStoreContext)!;\n // Read here rather than inside the request so a settings change re-creates\n // `analyze`, and a caller holding the old one cannot keep analysing under a\n // profile the author has already switched away from.\n const profileIds = useSettingsStore((state) => state.qualityProfileIds);\n const profileId = storedProfileId(profileIds);\n const gate = useSettingsStore((state) => state.qualityGate);\n const policyText = useSettingsStore(\n (state) => state.qualityPolicies?.[FORMAT]\n );\n\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n\n const cancel = useCallback(() => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n abortRef.current?.abort();\n abortRef.current = null;\n }, []);\n\n // An analysis that outlives its component would resolve into a store nobody\n // is reading, and worse, overwrite findings a later mount had already put\n // there.\n useEffect(() => cancel, [cancel]);\n\n /**\n * Commit a result unless a newer analysis has already landed.\n *\n * Aborting only reaches this hook instance's own request, and there are\n * several instances plus the build writing the same slice, so the ticket is\n * what actually orders them.\n */\n const commit = useCallback(\n (ticket: number, next: QualityState) => {\n if (isStaleQualityTicket(outputStore.getState().quality, ticket)) return;\n setOutput({ quality: next });\n },\n [outputStore, setOutput]\n );\n\n const run = useCallback(\n async (documentName: string, documentText: string) => {\n let jsonDefinition: unknown;\n try {\n jsonDefinition = JSON.parse(documentText);\n } catch {\n // Half-typed JSON is the normal state of an editor, not something to\n // report. Keep the previous findings on screen: blanking the panel on\n // every unbalanced brace is worse than showing findings one keystroke\n // out of date.\n return;\n }\n\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n const ticket = nextQualityTicket();\n\n const quality = buildQualityOptions(profileId, gate, policyText);\n\n try {\n const response = await fetch(API_ENDPOINTS.validate, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonDefinition,\n options: quality ? { quality } : {},\n }),\n signal: controller.signal,\n });\n const body = await readJsonBody(response);\n\n // A superseded request has already been replaced by a newer one, whose\n // answer is the current one; writing this stale result would undo it.\n if (controller.signal.aborted) return;\n\n if (!response.ok) {\n const message = describeFailure(response, body);\n if (body?.code === 'QUALITY_GATE_FAILED') {\n // A real rejection, and it carries the diagnostics that caused it.\n const findings = findingsFromAnalysis(body.quality);\n commit(ticket, {\n findings,\n counts: countBySeverity(findings),\n documentName,\n seq: ticket,\n blocked: true,\n source: 'validate',\n analyzedAt: Date.now(),\n gateError: message,\n });\n return;\n }\n // Everything else — 429 from the rate limiter, 413, a 500, a dead\n // proxy — says nothing about the document. Reporting it as a gate\n // failure would tell an author with no gate configured that their\n // document was blocked, and discarding the findings alongside it\n // would take away the only thing on screen that was still true.\n // Keeping the findings is only right while they still describe this\n // document. Across a tab switch they describe the previous one, and\n // every finding is a JSON Pointer into a file that is no longer on\n // screen — \"Apply fix\" would patch the wrong document. A stale\n // `blocked` is worse still: a gate rejection from before the switch\n // would keep the new document blocked on a transport error.\n const stored = outputStore.getState().quality;\n const previous =\n stored && stored.documentName === documentName ? stored : undefined;\n commit(ticket, {\n ...(previous ?? {\n findings: [],\n counts: { error: 0, warning: 0, info: 0 },\n source: 'validate' as const,\n }),\n documentName,\n seq: ticket,\n analyzedAt: Date.now(),\n blocked: previous?.blocked ?? false,\n gateError: undefined,\n analysisError: message,\n });\n return;\n }\n\n // `success` is false for a schema-invalid document and for a blocked\n // one, neither of which is a failed request — the analysis, when there\n // is one, is still in `data`.\n const analysis = body?.data?.qualityAnalysis;\n const findings = findingsFromAnalysis(analysis);\n commit(ticket, {\n findings,\n counts: countBySeverity(findings),\n ...readAnalysisFlags(analysis),\n documentName,\n seq: ticket,\n source: 'validate',\n analyzedAt: Date.now(),\n });\n } catch {\n // An abort lands here, and so does a dropped connection. Neither says\n // anything about the document, so the last analysis stands.\n } finally {\n if (abortRef.current === controller) abortRef.current = null;\n }\n },\n [commit, gate, outputStore, policyText, profileId]\n );\n\n const analyze = useCallback(\n (\n documentName: string,\n documentText: string,\n options?: { immediate?: boolean }\n ) => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n if (options?.immediate) {\n void run(documentName, documentText);\n return;\n }\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n void run(documentName, documentText);\n }, ANALYSIS_DEBOUNCE_MS);\n },\n [run]\n );\n\n return { analyze, cancel };\n}\n"],"names":["SEVERITIES","SEVERITY_RANK","UNRANKED","rankOf","severity","CERTAINTIES","FIX_OPS","UNCATEGORIZED","isRecord","value","looksLikeObject","asString","asSeverity","asCertainty","asStringArray","strings","entry","asEvidence","asFixes","fixes","op","path","fix","from","stripCodePrefix","message","code","prefix","makeId","index","buildFinding","fields","rawMessage","finding","ruleId","category","certainty","suggestion","relatedPaths","evidence","splitQualityWarnings","warnings","findings","others","warning","context","findingsFromAnalysis","analysis","diagnostics","diagnostic","countBySeverity","counts","compareFindings","a","b","rankA","rankB","pathA","pathB","codeA","codeB","groupByCategory","groups","bucket","worstRank","group","worst","rank","rankDelta","filterByMinSeverity","min","limit","certaintyLabel","c","EMPTY_POLICY_TEXT","isPlainObject","parseQualityPolicy","text","trimmed","parsed","error","EDITABLE_KEYS","unknownKey","key","known","rulesForFormat","rule","policy","configuration","QUALITY_PROFILES","storedProfileId","ids","id","FORMAT","isProfileForFormat","p","buildQualityOptions","profileId","gate","policyText","options","authored","hasGate","counter","nextQualityTicket","isStaleQualityTicket","current","ticket","ANALYSIS_DEBOUNCE_MS","readJsonBody","response","isJsonMediaType","readAnalysisFlags","source","describeFailure","body","useQualityAnalysis","setOutput","useOutputStore","state","outputStore","useContext","OutputStoreContext","profileIds","useSettingsStore","_a","timerRef","useRef","abortRef","cancel","useCallback","useEffect","commit","next","run","documentName","documentText","jsonDefinition","controller","quality","API_ENDPOINTS","stored","previous","_b"],"mappings":"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"}
@@ -9,7 +9,7 @@
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com" />
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
11
11
  <title>JSON to Office</title>
12
- <script type="module" crossorigin src="/assets/index-B7bDnXoC.js"></script>
12
+ <script type="module" crossorigin src="/assets/index-B4R-AAtI.js"></script>
13
13
  <link rel="modulepreload" crossorigin href="/assets/react-vendor-BmQ2Ko_D.js">
14
14
  <link rel="modulepreload" crossorigin href="/assets/query-vendor--rhYD8mC.js">
15
15
  <link rel="modulepreload" crossorigin href="/assets/radix-ui-DFNP5I4e.js">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@json-to-office/jto",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "JSON to Office CLI - Unified document and presentation generation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -83,13 +83,13 @@
83
83
  "vite": "6.0.5",
84
84
  "zod": "^4.0.0",
85
85
  "zustand": "5.0.2",
86
- "@json-to-office/core-pptx": "^1.5.0",
86
+ "@json-to-office/core-docx": "^1.7.0",
87
+ "@json-to-office/core-pptx": "^1.7.0",
87
88
  "@json-to-office/jto-cli": "^1.5.0",
88
89
  "@json-to-office/quality": "^1.6.0",
89
- "@json-to-office/core-docx": "^1.6.0",
90
90
  "@json-to-office/shared": "^1.6.0",
91
- "@json-to-office/shared-docx": "^1.6.0",
92
- "@json-to-office/shared-pptx": "^1.5.0"
91
+ "@json-to-office/shared-docx": "^1.7.0",
92
+ "@json-to-office/shared-pptx": "^1.7.0"
93
93
  },
94
94
  "devDependencies": {
95
95
  "@types/lodash.debounce": "4.0.9",