@json-to-office/jto 1.5.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 (36) 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-COHI5Wez.js → JsonEditorPage-CRY9589Y.js} +3 -3
  6. package/dist/client/assets/{JsonEditorPage-COHI5Wez.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-BO-KsLDk.js → editor-Y9dek093.js} +2 -2
  10. package/dist/client/assets/{editor-BO-KsLDk.js.map → editor-Y9dek093.js.map} +1 -1
  11. package/dist/client/assets/{editor-monaco-json-CJh63Fsz.js → editor-monaco-json-oBxvTIvi.js} +2 -2
  12. package/dist/client/assets/{editor-monaco-json-CJh63Fsz.js.map → editor-monaco-json-oBxvTIvi.js.map} +1 -1
  13. package/dist/client/assets/{editor-refs-store-CVLA_D5G.js → editor-refs-store-BMwl0ejj.js} +2 -2
  14. package/dist/client/assets/{editor-refs-store-CVLA_D5G.js.map → editor-refs-store-BMwl0ejj.js.map} +1 -1
  15. package/dist/client/assets/{index-otgy8_us.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-Ci_jNmHv.js → useQualityAnalysis-BFG1bJDp.js} +2 -2
  20. package/dist/client/assets/{useQualityAnalysis-Ci_jNmHv.js.map → useQualityAnalysis-BFG1bJDp.js.map} +1 -1
  21. package/dist/client/index.html +1 -1
  22. package/dist/client/templates/media/vermilion-annual-report/fonts/ClashDisplay-Bold.otf +0 -0
  23. package/dist/client/templates/media/vermilion-annual-report/fonts/ClashDisplay-Light.otf +0 -0
  24. package/dist/client/templates/media/vermilion-annual-report/fonts/ClashDisplay-Medium.otf +0 -0
  25. package/dist/client/templates/media/vermilion-annual-report/fonts/ClashDisplay-Regular.otf +0 -0
  26. package/dist/client/templates/media/vermilion-annual-report/fonts/ClashDisplay-Semibold.otf +0 -0
  27. package/dist/client/templates/media/vermilion-annual-report/fonts/FFL.txt +132 -0
  28. package/dist/client/templates/vermilion-annual-report.docx.json +10541 -0
  29. package/package.json +7 -7
  30. package/dist/client/assets/HomePage-ChJyDqzj.js +0 -106
  31. package/dist/client/assets/HomePage-ChJyDqzj.js.map +0 -1
  32. package/dist/client/assets/MonacoPluginProvider-prawCY6T.js +0 -3
  33. package/dist/client/assets/MonacoPluginProvider-prawCY6T.js.map +0 -1
  34. package/dist/client/assets/index-otgy8_us.js.map +0 -1
  35. package/dist/client/assets/preview-3fuCtJiz.js +0 -2
  36. package/dist/client/assets/preview-3fuCtJiz.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-otgy8_us.js";import{b as Y,f as G,d as T}from"./MonacoPluginProvider-prawCY6T.js";import{l as K}from"./HomePage-ChJyDqzj.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-Ci_jNmHv.js.map
6
+ //# sourceMappingURL=useQualityAnalysis-BFG1bJDp.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useQualityAnalysis-Ci_jNmHv.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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"}
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-otgy8_us.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">
@@ -0,0 +1,132 @@
1
+ Fontshare
2
+
3
+ ------------------------------------------------------------------
4
+ ITF Free Font License (FFL)
5
+ Version 2.0 - 17 Aug 2026
6
+ ------------------------------------------------------------------
7
+
8
+ Notice to User
9
+
10
+ Indian Type Foundry designs, produces and distributes font software as digital fonts to end users worldwide. In addition to commercial fonts that are available for a fee, ITF also offers several fonts that can be used free of charge. These free fonts are distributed through a dedicated platform called www.fontshare.com (“Fontshare”) to end users worldwide. Fonts identified on Fontshare as being licensed under the ITF Free Font License are subject to this legally binding EULA between Indian Type Foundry (“Indian Type Foundry”, “ITF” or “Licensor”) and you (“Licensee”).
11
+
12
+ You acknowledge that the Font Software and the designs embodied therein are protected by copyright, other intellectual property and industrial property rights, and international treaties. They are and shall remain at all times the intellectual property of Indian Type Foundry.
13
+
14
+ In addition to direct download, Fontshare may also make the Font Software available through the Fontshare API. In this case, the Font Software is delivered directly from servers used by Indian Type Foundry to the Licensee's website, without the Licensee having to download or host the Font Software.
15
+
16
+ By downloading, accessing, self-hosting, installing, storing, embedding, copying or otherwise using the Font Software, you agree to the following terms.
17
+
18
+
19
+ Definitions
20
+
21
+ “Font Software” refers to the computer files or programs released under this License that enable the display, reproduction and rendering of the typeface designs, including letters, numerals, punctuation, symbols, ornaments and other typographic characters. Font Software includes all font formats and bitmap or vector representations distributed by Indian Type Foundry under this License.
22
+
23
+ “Official Version” refers to the Font Software as officially distributed by Indian Type Foundry through Fontshare.
24
+
25
+ “Derivative Work” refers to any modified, adapted, altered or otherwise derived version of the Font Software or the typeface designs embodied therein, including modifications to font files (such as OTF, TTF, WOFF, WOFF2 or variable fonts), glyphs, characters, spacing, kerning, metrics, naming, subsetting, format conversion or other font data.
26
+
27
+ Derivative Work does not include any logo, document, graphic, image, website, application or other creative work created through the normal use of the Font Software, ownership of which remains with its respective creator or rights holder.
28
+
29
+
30
+ 01 Grant of License
31
+
32
+ You are hereby granted a non-exclusive, non-assignable, non-transferable and terminable license to access, download, install, store and use the Font Software for personal or commercial purposes, free of charge and for an unlimited period of time, subject to the terms of this License.
33
+
34
+ You may use the Font Software in any media, including Print, Websites, Mobile or Desktop Applications, Digital Images, Digital Advertising, Social Media, ePublishing, Video, Broadcasting and Games, at any scale and in any location worldwide.
35
+
36
+ You may self-host the Font Software on your own servers or infrastructure for use on your own websites and applications, including through standard webfont technologies such as CSS @font-face. Self-hosting by end users is permitted and recommended for greater control, reliability and performance. Use of the Fontshare API is optional and is not required for web use.
37
+
38
+ You may embed the Font Software in mobile or desktop applications and digital documents for the uses permitted under this License.
39
+
40
+ You may use the Font Software to create logos, wordmarks, graphic elements, images, vector files, scalable drawings and other creative works. Logos and wordmarks created using the Font Software may be registered as trademarks. Any rights in the resulting creative works, including any trademark rights, remain with their respective creator or rights holder. Such rights do not grant any ownership or exclusive rights in the Font Software or the underlying typeface designs.
41
+
42
+ You may use the Font Software on any number of devices, including computers, tablets and phones. The number of output devices, including printers, is not restricted. Where the Licensee is an organization, the Font Software may be installed, stored, copied and shared internally among its employees for the Licensee's own permitted uses under this License. Such internal sharing does not constitute redistribution under this License.
43
+
44
+ You may make a reasonable number of backup copies of the Font Software as necessary for your permitted use.
45
+
46
+ You may, but are not required to, identify or credit Indian Type Foundry or Fontshare in works created using the Font Software.
47
+
48
+
49
+ 02 Limitations of Usage
50
+
51
+ You may not modify, edit, adapt, translate, reverse engineer, decompile, disassemble or otherwise alter the Font Software or the typeface designs embodied therein, in whole or in part, without the prior written consent of the Licensor. This includes modifying or replacing glyphs, subsetting, format conversion, or altering font names, copyright information, ownership information or other metadata.
52
+
53
+ The Font Software may not, beyond the permitted copies and uses defined herein, be distributed, duplicated, loaned, resold, sublicensed, transferred, donated, given away or otherwise made available to any other person or entity, whether for free or for a fee. This includes distributing the Font Software through another font website, font library, marketplace, repository, download service, application or platform, or by email, removable media, publicly accessible servers, file-sharing services, peer-to-peer networks or any other means.
54
+
55
+ You may not provide the Font Software directly to external designers, agencies, contractors, printers or other service providers. Any third party wishing to use the Font Software must obtain their own copy directly from Fontshare and be independently bound by this License.
56
+
57
+ You may not host, serve, embed or otherwise make the Font Software available for use by third parties through any website, application, online service, SaaS platform, design tool, template editor or similar service. This includes making the Font Software available as a selectable font for third-party users to create, edit, customize or generate their own content, even where such users cannot download or extract the Font Software itself.
58
+
59
+ For the avoidance of doubt, nothing in this Section 02 restricts the self-hosting, embedding or other use of the Font Software by the Licensee for the Licensee's own websites, applications or other permitted uses under Section 01.
60
+
61
+
62
+ 03 Embedding
63
+
64
+ You may embed the Font Software in PDFs and other digital documents for your permitted uses under this License, provided that the embeddedFont Software cannot be extracted or used independently from the document.
65
+
66
+ Documents containing embedded Font Software may be distributed without restriction as to the number of recipients.
67
+
68
+ The extraction of the Font Software, in whole or in part, for independent use is prohibited.
69
+
70
+
71
+ 04 Third-party use, Commercial print service providers
72
+
73
+ You may provide documents, artwork or other files created using the Font Software to commercial printers and other service providers for printing, production or display purposes.
74
+
75
+ If a service provider requires access to the Font Software itself, they must obtain their own copy directly from Fontshare and be independently bound by this License.
76
+
77
+
78
+ 05 Derivative Work
79
+
80
+ You may not create a Derivative Work from the Font Software without the prior written consent of the Licensor.
81
+
82
+ Where the Licensor expressly permits the creation of a Derivative Work, such permission is limited to the specific use authorized by the Licensor and does not grant any ownership or other rights in the Official Version of the Font Software or the underlying typefacedesigns.
83
+
84
+ A Derivative Work may not be sublicensed, sold, leased, rented, loaned, distributed, transferred or otherwise made available to any third party without the prior written consent of the Licensor.
85
+
86
+
87
+ 06 Warranty and Liability
88
+
89
+ THE FONT SOFTWARE IS PROVIDED FREE OF CHARGE AND ON AN “AS IS” AND “AS AVAILABLE” BASIS. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, INDIAN TYPE FOUNDRY MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, COMPATIBILITY, AVAILABILITY OR OTHERWISE.
90
+
91
+ TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, INDIAN TYPE FOUNDRY SHALL NOT BE LIABLE TO THE LICENSEEOR ANY THIRD PARTY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES, INCLUDING LOSS OF PROFITS, REVENUE, DATA, BUSINESS OR SAVINGS, OR BUSINESS INTERRUPTION, ARISING OUT OF THE USE OF OR INABILITY TO USE THE FONT SOFTWARE.
92
+
93
+ IF THE LICENSEE CHOOSES TO ACCESS OR SERVE THE FONT SOFTWARE THROUGH THE FONTSHARE API, THE LICENSEE DOES SO AT ITS OWN RISK. THE FONTSHARE API IS PROVIDED AS A CONVENIENCE AND DOES NOT FORM PART OF THE RIGHTS GRANTED TO THE LICENSEE TO USE THE FONT SOFTWARE UNDER THIS LICENSE.
94
+
95
+ INDIAN TYPE FOUNDRY DOES NOT GUARANTEE THE AVAILABILITY, PERFORMANCE OR CONTINUED OPERATION OF THE FONTSHARE API. INDIAN TYPE FOUNDRY MAY, AT ITS SOLE DISCRETION AND WITHOUT PRIOR NOTICE, MODIFY, RESTRICT, SUSPEND OR DISCONTINUE ACCESS TO THE FONTSHARE API, WHETHER GENERALLY OR FOR ANY PARTICULAR USER, WEBSITE OR APPLICATION. INDIAN TYPE FOUNDRY SHALL NOT BE RESPONSIBLE OR LIABLE FOR ANY INTERRUPTION, MALFUNCTION, DOWNTIME, CHANGE, RESTRICTION OR DISCONTINUATION OF THE API, OR FOR ANY RESULTING IMPACT ON THE LICENSEE'S WEBSITE, APPLICATION OR OTHER SERVICES.
96
+
97
+
98
+ 07 Updates, Maintenance and Support Services
99
+
100
+ Indian Type Foundry is under no obligation to provide updates, upgrades, maintenance, technical support or other support services for the Font Software under this License.
101
+
102
+ Indian Type Foundry may, at its sole discretion, update, modify, replace or discontinue any Font Software made available through Fontshare.
103
+
104
+
105
+ 08 Termination
106
+
107
+ Any breach of the terms of this License shall be grounds for termination. Where the breach is capable of being remedied, the Licensor may notify the Licensee in writing and require the breach to be remedied within 30 days of receipt of such notice.
108
+
109
+ Notwithstanding the foregoing, the Licensor may terminatethis License immediately in the event of any deliberate or material unauthorized modification, redistribution, sale, sublicensing, transfer or other misuse of the Font Software or the intellectual property rights of Indian Type Foundry.
110
+
111
+ Upon termination, and without limitation of any other remedies available under law or equity, the Licensee must immediately cease all use of the Font Software and delete the Font Software and all copies thereof inits possession or control. Proof of deletion must be provided upon request of the Licensor.
112
+
113
+ Indian Type Foundry reserves the right to seek damages and any other remedies available under applicable law for any breach of this License.
114
+
115
+
116
+ 09 Final Provisions
117
+
118
+ If any provision of this License is found to be invalid, illegal or unenforceable, the remaining provisions shall remain in full force and effect. Any invalid, illegal or unenforceable provision shall, to the extent legally permissible, be interpreted or replaced in a manner that most closely reflects its original purpose.
119
+
120
+ The failure of Indian Type Foundry to enforce any provision of this License shall not constitute a waiver of that provision or of its right to enforce it subsequently.
121
+
122
+ The uses expressly permitted under this License require no additional license or fee. If a Licensee requires additional rights or permissions, exceptions to any restrictions under this License, modifications or customizations to the Font Software or typeface designs, additional styles, weights, widths, characters or language extensions, technical support ormaintenance services, warranties or indemnities, or other customized licensing terms, they may contact Indian Type Foundry to discuss a separate custom license or arrangement.
123
+
124
+ This License shall be governed by the laws ofthe Republic of India. To the extent legally permissible, the courts located in Ahmedabad, Gujarat, India shall have exclusive jurisdiction over any dispute arising out of or in connection with this License.
125
+
126
+ ------------------------------------------------------------------
127
+ ITF Free Font License (FFL)
128
+ Version 2.0 - 17 Aug 2026
129
+
130
+ Copyright © 2026 Indian Type Foundry. All rights reserved.
131
+ https://www.fontshare.com
132
+ ------------------------------------------------------------------