llm browser

This commit is contained in:
developer committed 2026-10-19 15:25:27 +00:00
1 parent afed023152
commit a4dbb75195
39 files changed
+3709 -58

No files matched your search

Binary file not shown.
+75
View File
@@ -0,0 +1,75 @@
{
"tokens": [
"src/tokens.css"
],
"pairs": [
{
"fg": "--accent-foreground",
"bg": "--accent",
"min": 4.5
},
{
"fg": "--card-foreground",
"bg": "--card",
"min": 4.5
},
{
"fg": "--muted-foreground",
"bg": "--muted",
"min": 4.5
},
{
"fg": "--popover-foreground",
"bg": "--popover",
"min": 4.5
},
{
"fg": "--primary-foreground",
"bg": "--primary",
"min": 4.5
},
{
"fg": "--secondary-foreground",
"bg": "--secondary",
"min": 4.5
},
{
"fg": "--sidebar-accent-foreground",
"bg": "--sidebar-accent",
"min": 4.5
},
{
"fg": "--sidebar-foreground",
"bg": "--sidebar",
"min": 4.5
},
{
"fg": "--sidebar-primary-foreground",
"bg": "--sidebar-primary",
"min": 4.5
},
{
"fg": "--foreground",
"bg": "--background",
"min": 4.5
},
{
"fg": "--foreground",
"bg": "--card",
"min": 4.5
},
{
"fg": "--muted-foreground",
"bg": "--background",
"min": 4.5
}
],
"baseline": {
"src/App.css": 16,
"src/components/CodeEditor.tsx": 3,
"src/components/MarkdownRenderer.tsx": 2,
"src/data/library.ts": 8,
"src/utils.css": 9,
"tests/check-tokens.test.mjs": 5
}
}
+45
View File
@@ -0,0 +1,45 @@
# LLM Browser — Agent View (Northstar design system)
## Audience & purpose
A validator tool for people who publish sites for LLM agents: enter a domain,
see exactly what a text-only agent sees. Three surfaces per inspection:
rendered markdown, the `llms.txt` family (raw + parsed), and the
agent-readiness checks. Shared with the Northstar showcase app (routes,
primitives), so all components come from `src/components/ui`.
## Visual character
Inherited from the host system: lab()-based tokens, quiet surfaces, mono for
machine output (URLs, raw files, source), sans for prose. Nothing on the
agent-view page introduces new colours — every shade is a token or a
`color-mix()` of one, so dark/light parity is automatic.
## Layout
- Single-column app pattern: form card → status header → tabbed results.
- `?url=<domain>` in the address bar is the source of truth for what is being
inspected; link clicks rewrite it (the agent loop), so any view is a link.
- Tabs: Markdown (rendered), Source (raw markdown + copy), llms.txt (one card
per candidate path: `/llms.txt`, `/llms-full.txt`, `/.well-known/llms.txt`),
robots.txt, Meta (definition list), Checks (badge + label + detail rows).
## Component patterns
- Status: `Badge` variants carry state — positive/ok, primary/warn,
destructive/error, secondary/info. No extra colours for severities.
- File cards: `Card` + `CardHeader`, status badge, absolute URL in mono,
parsed doc first, raw content in a `<details>` below.
- Markdown output renders through `src/components/markdown/MarkdownView`
(React elements, no HTML injection); links are intercepted and re-enter the
viewer instead of navigating away.
- Empty, loading (Skeleton), and error states are explicit — an inspection can
fail at the network, the site, or validation, and each gets its own copy.
## Accepted decisions
- The light theme's `--destructive` stays at the default red
(`lab(48.4493% 77.4328 61.5452)`): its brighter variant failed the 4.5:1
pair with `--destructive-foreground`. Dark keeps the brighter red (passes on
dark surfaces). Theme rules in `src/tokens.css` use explicit selectors only
(no `:not([data-theme='light'])` fallback) so tooling resolves them cleanly.
- Agent-view styles live in `src/App.css` under "Agent view"; they override
`.markdown-body`'s article metrics (`.agent-doc`) and add link, list, table,
quote and code styling the showcase never needed.
- Severity mapping: ok→positive, info→secondary, warn→primary,
error→destructive. Uppercase mono badges in the checks list.
+1
View File
@@ -0,0 +1 @@
@import "https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Manrope:wght@400;500;600;700;800&display=swap";:root{--root-font-size:112%;--text-3xs:.5625rem;--text-2xs:.625rem;--text-xs:.6875rem;--text-sm:.75rem;--text-md:.875rem;--text-base:1rem;--text-lg:1.3125rem;--text-xl:1.5625rem;--text-2xl:1.75rem;--text-hero:clamp(2.625rem, 5vw, 4.375rem);--font-sans:"Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-mono:"DM Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;--space-1:.25rem;--space-2:.5rem;--space-3:.75rem;--space-4:1rem;--space-5:1.25rem;--space-6:1.5rem;--space-7:2rem;--space-8:2.5rem;--space-9:3rem;--space-10:4rem;--space-11:5rem;--radius-sm:5px;--radius-md:6px;--radius-lg:9px;--radius-xl:12px;--radius-2xl:14px;--radius-pill:9999px;--focus-ring:2px solid var(--ring);--focus-offset:2px}:root,:root[data-theme=light],.light{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--background:lab(100% 0 0);--foreground:lab(2.75381% 0 0);--card:lab(97.68% -.0000298023 .0000119209);--card-foreground:lab(2.75381% 0 0);--popover:lab(100% 0 0);--popover-foreground:lab(2.75381% 0 0);--primary:lab(7.78201% -.0000149012 0);--primary-foreground:lab(98.26% 0 0);--secondary:lab(94.2% 0 0);--secondary-foreground:lab(7.78201% -.0000149012 0);--muted:lab(94.2% 0 0);--muted-foreground:lab(39.68% 0 0);--accent:lab(94.2% 0 0);--accent-foreground:lab(7.78201% -.0000149012 0);--destructive:lab(48.4493% 77.4328 61.5452);--destructive-foreground:lab(98.26% 0 0);--border:lab(86.08% -.0000298023 0);--input:lab(86.08% -.0000298023 0);--ring:lab(42% 0 0);--chart-1:lab(57.1026% 64.2584 89.8886);--chart-2:lab(55.0223% -41.0774 -3.90277);--chart-3:lab(30.372% -13.1853 -18.7887);--chart-4:lab(80.1641% 16.6016 99.2089);--chart-5:lab(72.7183% 31.8672 97.9407);--sidebar:lab(96.52% -.0000298023 .0000119209);--sidebar-foreground:lab(2.75381% 0 0);--sidebar-primary:lab(7.78201% -.0000149012 0);--sidebar-primary-foreground:lab(98.26% 0 0);--sidebar-accent:lab(94.2% 0 0);--sidebar-accent-foreground:lab(7.78201% -.0000149012 0);--sidebar-border:lab(86.08% -.0000298023 0);--sidebar-ring:lab(42% 0 0);--dot-color:lab(7.78201% -.0000149012 0/.05)}:root[data-theme=dark],.dark{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--background:lab(0% 0 0);--foreground:lab(94.78% 0 0);--card:lab(.903296% 0 0);--card-foreground:lab(94.78% 0 0);--popover:lab(.903296% 0 0);--popover-foreground:lab(94.78% 0 0);--primary:lab(89.9321% -47.9163 -15.7303);--primary-foreground:lab(0% 0 0);--secondary:lab(2.47865% 0 0);--secondary-foreground:lab(94.78% 0 0);--muted:lab(2.47865% 0 0);--muted-foreground:lab(60.56% 0 0);--accent:lab(1.98454% 0 0);--accent-foreground:lab(89.9321% -47.9163 -15.7303);--destructive:lab(54.5811% 71.684 50.9279);--destructive-foreground:lab(100% 0 0);--border:lab(4.8411% 0 0);--input:lab(1.98454% 0 0);--ring:lab(89.9321% -47.9163 -15.7303);--chart-1:lab(89.9321% -47.9163 -15.7303);--chart-2:lab(66.9756% -58.27 19.5419);--chart-3:lab(72.7183% 31.8672 97.9407);--chart-4:lab(52.0183% 66.11 -78.2316);--chart-5:lab(56.101% 79.4328 31.4532);--sidebar:lab(.498049% 0 0);--sidebar-foreground:lab(94.78% 0 0);--sidebar-primary:lab(89.9321% -47.9163 -15.7303);--sidebar-primary-foreground:lab(0% 0 0);--sidebar-accent:lab(1.98454% 0 0);--sidebar-accent-foreground:lab(89.9321% -47.9163 -15.7303);--sidebar-border:lab(4.8411% 0 0);--sidebar-ring:lab(89.9321% -47.9163 -15.7303);--dot-color:lab(89.9321% -47.9163 -15.7303/.045)}html{font-size:var(--root-font-size)}.dot-canvas{background-image:radial-gradient(var(--dot-color) 1px, transparent 1px);background-size:24px 24px}.dot-canvas-dense{background-image:radial-gradient(var(--dot-color) 1px, transparent 1px);background-size:16px 16px}.flex-center{justify-content:center;align-items:center;display:flex}.flex-between{justify-content:space-between;align-items:center;display:flex}.flex-col{flex-direction:column;display:flex}.stack-xs{gap:var(--space-1);flex-direction:column;display:flex}.stack-sm{gap:var(--space-2);flex-direction:column;display:flex}.stack-md{gap:var(--space-4);flex-direction:column;display:flex}.stack-lg{gap:var(--space-6);flex-direction:column;display:flex}.row-xs{align-items:center;gap:var(--space-1);display:flex}.row-sm{align-items:center;gap:var(--space-2);display:flex}.row-md{align-items:center;gap:var(--space-4);display:flex}.grid-2{gap:var(--space-4);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.grid-3{gap:var(--space-4);grid-template-columns:repeat(3,minmax(0,1fr));display:grid}.grid-4{gap:var(--space-4);grid-template-columns:repeat(4,minmax(0,1fr));display:grid}@media (width<=760px){.grid-2,.grid-3,.grid-4{grid-template-columns:1fr}}.surface-card{background:var(--card);color:var(--card-foreground);border:1px solid var(--border);border-radius:var(--radius-lg);padding:var(--space-5);box-shadow:0 4px 20px #0000001f}.surface-sidebar{background:var(--sidebar);color:vaLine truncated
+27
View File
@@ -0,0 +1,27 @@
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,o)=>(o=n==null?{}:e(i(n)),s(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var l=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.for(`react.view_transition`),m=Symbol.iterator;function h(e){return typeof e!=`object`||!e?null:(e=m&&e[m]||e[`@@iterator`],typeof e==`function`?e:null)}var g={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},_=Object.assign,v={};function y(e,t,n){this.props=e,this.context=t,this.refs=v,this.updater=n||g}y.prototype.isReactComponent={},y.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},y.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function b(){}b.prototype=y.prototype;function ee(e,t,n){this.props=e,this.context=t,this.refs=v,this.updater=n||g}var te=ee.prototype=new b;te.constructor=ee,_(te,y.prototype),te.isPureReactComponent=!0;var ne=Array.isArray;function re(){}var x={H:null,A:null,T:null,S:null},ie=Object.prototype.hasOwnProperty;function S(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function ae(e,t){return S(e.type,t,e.props)}function oe(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function se(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var ce=/\/+/g;function le(e,t){return typeof e==`object`&&e&&e.key!=null?se(``+e.key):t.toString(36)}function ue(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(re,re):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function de(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,de(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+le(e,0):a,ne(o)?(i=``,c!=null&&(i=c.replace(ce,`$&/`)+`/`),de(o,r,i,``,function(e){return e})):o!=null&&(oe(o)&&(o=ae(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(ce,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(ne(e))for(var u=0;u<e.length;u++)a=e[u],s=l+le(a,u),c+=de(a,r,i,s,o);else if(u=h(e),typeof u==`function`)for(e=u.call(e),u=0;!(a=e.next()).done;)a=a.value,s=l+le(a,u++),c+=de(a,r,i,s,o);else if(s===`object`){if(typeof e.then==`function`)return de(ue(e),r,i,a,o);throw r=String(e),Error(`Objects are not valid as a React child (found: `+(r===`[object Object]`?`object with keys {`+Object.keys(e).join(`, `)+`}`:r)+`). If you meant to render a collection of children, use an array instead.`)}return c}function fe(e,t,n){if(e==null)return e;var r=[],i=0;return de(e,r,``,``,function(e){return t.call(n,e,i++)}),r}function pe(e){if(e._status===-1){var t=e._result,n=t();n.then(function(t){(e._status===0||e._status===-1)&&(e._status=1,e._result=t,n.status===void 0&&(n.status=`fulfilled`,n.value=t))},function(t){(e._status===0||e._status===-1)&&(e._status=2,e._result=t,n.status===void 0&&(n.status=`rejected`,n.reason=t))}),e._status===-1&&(e._statuLine truncated
at`)?` (<anonymous>)`:-1<e.stack.indexOf(`@`)?`@unknown:0:0`:``}return`
`+Ve+e+He}var We=!1;function E(e,t){if(!e||We)return``;We=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var r={DetermineComponentFrameRoot:function(){try{if(t){var n=function(){throw Error()};if(Object.defineProperty(n.prototype,"props",{set:function(){throw Error()}}),typeof Reflect==`object`&&Reflect.construct){try{Reflect.construct(n,[])}catch(e){var r=e}Reflect.construct(e,[],n)}else{try{n.call()}catch(e){r=e}n=!1;try{var i=Object.getOwnPropertyDescriptor(e.prototype,`props`);Object.defineProperty(e.prototype,"props",{configurable:!0,set:function(){throw Error()}}),n=!0,new e}finally{n&&(i===void 0?delete e.prototype.props:Object.defineProperty(e.prototype,"props",i))}}}else{try{throw Error()}catch(e){r=e}(n=e())&&typeof n.catch==`function`&&n.catch(function(){})}}catch(e){if(e&&r&&typeof e.stack==`string`)return[e.stack,r.stack]}return[null,null]}};r.DetermineComponentFrameRoot.displayName=`DetermineComponentFrameRoot`;var i=Object.getOwnPropertyDescriptor(r.DetermineComponentFrameRoot,`name`);i&&i.configurable&&Object.defineProperty(r.DetermineComponentFrameRoot,"name",{value:`DetermineComponentFrameRoot`});var a=r.DetermineComponentFrameRoot(),o=a[0],s=a[1];if(o&&s){var c=o.split(`
`),l=s.split(`
`);for(i=r=0;r<c.length&&!c[r].includes(`DetermineComponentFrameRoot`);)r++;for(;i<l.length&&!l[i].includes(`DetermineComponentFrameRoot`);)i++;if(r===c.length||i===l.length)for(r=c.length-1,i=l.length-1;1<=r&&0<=i&&c[r]!==l[i];)i--;for(;1<=r&&0<=i;r--,i--)if(c[r]!==l[i]){if(r!==1||i!==1)do if(r--,i--,0>i||c[r]!==l[i]){var u=`
`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(`<anonymous>`)&&(u=u.replace(`<anonymous>`,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{We=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?Ue(n):``}function Ge(e,t){switch(e.tag){case 26:case 27:case 5:return Ue(e.type);case 16:return Ue(`Lazy`);case 13:return e.child!==t&&t!==null?Ue(`Suspense Fallback`):Ue(`Suspense`);case 19:return Ue(`SuspenseList`);case 0:case 15:return E(e.type,!1);case 11:return E(e.type.render,!1);case 1:return E(e.type,!0);case 31:return Ue(`Activity`);case 30:return Ue(`ViewTransition`);default:return``}}function Ke(e){try{var t=``,n=null;do t+=Ge(e,n),n=e,e=e.return;while(e);return t}catch(e){return`
Error generating stack: `+e.message+`
`+e.stack}}var qe=Object.prototype.hasOwnProperty,Je=t.unstable_scheduleCallback,Ye=t.unstable_cancelCallback,Xe=t.unstable_shouldYield,Ze=t.unstable_requestPaint,D=t.unstable_now,Qe=t.unstable_getCurrentPriorityLevel,$e=t.unstable_ImmediatePriority,et=t.unstable_UserBlockingPriority,tt=t.unstable_NormalPriority,nt=t.unstable_LowPriority,rt=t.unstable_IdlePriority,O=t.log,it=t.unstable_setDisableYieldValue,at=null,ot=null;function st(e){if(typeof O==`function`&&it(e),ot&&typeof ot.setStrictMode==`function`)try{ot.setStrictMode(at,e)}catch{}}var ct=Math.clz32?Math.clz32:dt,lt=Math.log,ut=Math.LN2;function dt(e){return e>>>=0,e===0?32:31-(lt(e)/ut|0)|0}var ft=256,pt=262144,mt=4194304;function ht(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&-e;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function gt(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=ht(n))):i=ht(o):i=ht(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=ht(n))):i=ht(o)):i=ht(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function _t(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function vt(e,t){t&8&&(t|=t&32);var n=e.entangledLanes;if(n!==0)for(e=e.entanglements,n&=t;0<n;){var r=31-ct(n),i=1<<r;t|=e[r],n&=~i}return t}function yt(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function k(){var e=mt;return mt<<=1,!(mt&62914560)&&(mt=4194304),e}function bt(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function xt(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function St(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0<n;){var u=31-ct(n),d=1<<u;s[u]=0,c[u]=-1;var f=l[u];if(f!==null)for(l[u]=null,u=0;u<f.length;u++){var p=f[u];p!==null&&(p.lane&=-536870913)}n&=~d}r!==0&&Ct(e,r,0),a!==0&&i===0&&e.tag!==0&&(e.suspendedLanes|=a&~(o&~t))}function Ct(e,t,n){e.pendingLanes|=t,e.suspendedLanes&=~t;var r=31-ct(t);e.entangledLanes|=t,e.entanglements[r]=e.entanglements[r]|1073741824|n&261930}function wt(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-ct(n),i=1<<r;i&t|e[r]&t&&(e[r]|=t),n&=~i}}function Tt(e,t){var n=t&-t;return n=n&42?1:Et(n),(n&(e.suspendedLanes|t))===0?n:0}function Et(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:e=128;break;case 268435456:e=134217728;break;default:e=0}return e}function Dt(e){return e&=-e,2<e?8<e?e&134217727?32:268435456:8:2}function Ot(){var e=w.p;return e===0?(e=window.event,e===void 0?32:Ch(e.type)):e}function kt(e,t){var n=w.p;try{return w.p=e,t()}finally{w.p=n}}var At=Math.random().toString(36).slice(2),jt=`__reactFiber$`+At,Mt=`__reactProps$`+At,Nt=`__reactContainer$`+At,Pt=`__reactEvents$`+At,Ft=`__reactListeners$`+At,It=`__reactHandles$`+At,Lt=`__reactResources$`+At,Rt=`__reactMarker$`+At,zt=`__reactLoad$`+At;function Bt(e){delete e[jt],delete e[Mt],delete e[Ft],delete e[It]}function Vt(e){var t;if(t=e[jt])return t;for(var n=e.parentNode;n;){if(t=n[Nt]||n[jt]){if(n=t.alternate,t.child!==null||n!==null&&n.child!==null)for(e=fm(e);e!==null;){if(n=e[jt])return n;e=fm(e)}return t}e=n,n=e.parentNode}return null}function Ht(e){if(e=e[jt]||e[Nt]){var t=e.tag;if(t===5||t===6||t===13||t===31||t===26||t===27||t===3)return e}return null}function Ut(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e.stateNode;throw Error(i(33))}function Wt(e){var t=e[Lt];return t||=e[Lt]={hoistableStyles:new Map,hoistableScripts:new Map},t}function Gt(e){e[Rt]=!0}function Kt(e){e[zt]=void 0}var qt=new Set,Jt={};function Yt(e,t){Xt(e,t),Xt(e+`Capture`,t)}function Xt(e,t){for(Jt[e]=t,e=0;e<t.length Line truncated
`).replace(Qf,``)}function ep(e,t){return t=$f(t),$f(e)===t}function $(e,t,n,r,a,o){switch(n){case`children`:if(typeof r==`string`)t===`body`||t===`textarea`&&r===``||yn(e,r);else if(typeof r==`number`||typeof r==`bigint`)t!==`body`&&yn(e,``+r);else return;break;case`className`:rn(e,`class`,r);break;case`tabIndex`:rn(e,`tabindex`,r);break;case`dir`:case`role`:case`viewBox`:case`width`:case`height`:rn(e,n,r);break;case`style`:Sn(e,r,o);return;case`data`:if(t!==`object`){rn(e,`data`,r);break}case`src`:case`href`:if(r===``&&(t!==`a`||n!==`href`)){e.removeAttribute(n);break}if(r==null||typeof r==`function`||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=En(r),e.setAttribute(n,r);break;case`action`:case`formAction`:if(typeof r==`function`){e.setAttribute(n,`javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')`);break}if(typeof o==`function`&&(n===`formAction`?(t!==`input`&&$(e,t,`name`,a.name,a,null),$(e,t,`formEncType`,a.formEncType,a,null),$(e,t,`formMethod`,a.formMethod,a,null),$(e,t,`formTarget`,a.formTarget,a,null)):($(e,t,`encType`,a.encType,a,null),$(e,t,`method`,a.method,a,null),$(e,t,`target`,a.target,a,null))),r==null||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=En(r),e.setAttribute(n,r);break;case`onClick`:r!=null&&(e.onclick=Dn);return;case`onScroll`:r!=null&&Q(`scroll`,e);return;case`onScrollEnd`:r!=null&&Q(`scrollend`,e);return;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));o?.__html!==n&&(e.innerHTML=n)}}break;case`multiple`:e.multiple=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`muted`:e.muted=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`suppressContentEditableWarning`:case`suppressHydrationWarning`:case`defaultValue`:case`defaultChecked`:case`innerHTML`:case`ref`:break;case`autoFocus`:break;case`xlinkHref`:if(r==null||typeof r==`function`||typeof r==`boolean`||typeof r==`symbol`){e.removeAttribute(`xlink:href`);break}n=En(r),e.setAttributeNS(`http://www.w3.org/1999/xlink`,`xlink:href`,n);break;case`contentEditable`:case`spellCheck`:case`draggable`:case`value`:case`autoReverse`:case`externalResourcesRequired`:case`focusable`:case`preserveAlpha`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,r):e.removeAttribute(n);break;case`inert`:case`allowFullScreen`:case`async`:case`autoPlay`:case`controls`:case`credentialless`:case`default`:case`defer`:case`disabled`:case`disablePictureInPicture`:case`disableRemotePlayback`:case`formNoValidate`:case`hidden`:case`loop`:case`noModule`:case`noValidate`:case`open`:case`playsInline`:case`readOnly`:case`required`:case`reversed`:case`scoped`:case`seamless`:case`itemScope`:r&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,``):e.removeAttribute(n);break;case`capture`:case`download`:!0===r?e.setAttribute(n,``):!1!==r&&r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,r):e.removeAttribute(n);break;case`cols`:case`rows`:case`size`:case`span`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`&&!isNaN(r)&&1<=r?e.setAttribute(n,r):e.removeAttribute(n);break;case`rowSpan`:case`start`:r==null||typeof r==`function`||typeof r==`symbol`||isNaN(r)?e.removeAttribute(n):e.setAttribute(n,r);break;case`popover`:Q(`beforetoggle`,e),Q(`toggle`,e),nn(e,`popover`,r);break;case`xlinkActuate`:an(e,`http://www.w3.org/1999/xlink`,`xlink:actuate`,r);break;case`xlinkArcrole`:an(e,`http://www.w3.org/1999/xlink`,`xlink:arcrole`,r);break;case`xlinkRole`:an(e,`http://www.w3.org/1999/xlink`,`xlink:role`,r);break;case`xlinkShow`:an(e,`http://www.w3.org/1999/xlink`,`xlink:show`,r);break;case`xlinkTitle`:an(e,`http://www.w3.org/1999/xlink`,`xlink:title`,r);break;case`xlinkType`:an(e,`http://www.w3.org/1999/xlink`,`xlink:type`,r);break;case`xmlBase`:an(e,`http://www.w3.org/XML/1998/namespace`,`xml:base`,r);break;case`xmlLang`:an(e,`http://www.w3.org/XML/1998/namespace`,`xml:lang`,r);break;case`xmlSpace`:an(e,`http://www.w3.org/XML/1998/namespace`,`xml:space`,r);break;case`is`:nn(e,`is`,r);break;case`innerText`:case`textContent`:return;default:if(!(2<n.length)||n[0]!==`o`&&n[0]!==`O`||n[1]!==`n`&&n[1]!==`N`)n=wn.get(n)||n,nn(e,n,r);else return}A=!0}function tp(e,t,n,r,a,o){switch(n){case`style`:Sn(e,r,o);return;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));o?.__html!==n&&(e.innerHTML=n)}}break;case`children`:if(typeof r==`string`)yn(e,r);else if(typeof r==`number`||typeof r==`bigint`)yn(e,``+r);else return;break;case`onScroll`:r!=null&&Q(`scroll`,e);return;case`onScrollEnd`:r!=null&&Q(`scrollend`,e);return;case`onClick`:r!=null&&(e.onclick=D Line truncated
export function `],[`blue`,`DeployAction`],[`plain`,`() {
return (
`],[`plain`,` <Button variant="primary">Deploy changes</Button>
)
}`]];function Li(){let[e,t]=(0,_.useState)(!1),{schedule:n}=nr();return(0,E.jsxs)(`div`,{className:`code-card`,children:[(0,E.jsxs)(`div`,{className:`code-head`,children:[(0,E.jsx)(`span`,{className:`mono muted`,children:`button.tsx`}),(0,E.jsxs)(`button`,{className:`copy-button`,onClick:()=>{navigator.clipboard?.writeText(Fi),t(!0),n(()=>t(!1),1600)},children:[(0,E.jsx)(j,{name:e?`check`:`copy`,size:14}),e?`Copied`:`Copy code`]})]}),(0,E.jsx)(`pre`,{children:(0,E.jsx)(`code`,{children:Ii.map(([e,t],n)=>e===`plain`?(0,E.jsx)(`span`,{children:t},n):(0,E.jsx)(`span`,{className:e,children:t},n))})})]})}function Ri(){return(0,E.jsx)(Li,{})}function zi({title:e=`Quiet power, shipped weekly.`,kicker:t=`NORTHSTAR / RELEASE NOTES`}){return(0,E.jsxs)(`div`,{className:`markdown-card`,children:[(0,E.jsxs)(`div`,{className:`markdown-toolbar`,children:[(0,E.jsx)(`span`,{className:`eyebrow`,children:`Preview`}),(0,E.jsx)(`span`,{className:`mono muted`,children:`release-notes.md`})]}),(0,E.jsxs)(`article`,{className:`markdown-body`,children:[(0,E.jsx)(`span`,{className:`md-kicker`,children:t}),(0,E.jsx)(`h3`,{children:e}),(0,E.jsx)(`p`,{children:`Our components help teams move quickly without making the interface feel rushed.`}),(0,E.jsxs)(`div`,{className:`md-callout`,children:[(0,E.jsx)(`span`,{className:`callout-mark`,children:`✦`}),(0,E.jsxs)(`p`,{children:[(0,E.jsx)(`strong`,{children:`New in 0.8.2`}),(0,E.jsx)(`br`,{}),`Button focus rings now respect your theme while keeping a dependable contrast ratio.`]})]}),(0,E.jsx)(`h4`,{children:`What changed`}),(0,E.jsxs)(`ul`,{children:[(0,E.jsxs)(`li`,{children:[`Added `,(0,E.jsx)(`code`,{children:`accent-subtle`}),` surface token`]}),(0,E.jsx)(`li`,{children:`Improved keyboard navigation in menus`}),(0,E.jsx)(`li`,{children:`Reduced layout shift during loading states`})]}),(0,E.jsxs)(`div`,{className:`md-table`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Token`}),(0,E.jsx)(`span`,{children:`Value`}),(0,E.jsx)(`span`,{children:`Role`})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`code`,{children:`surface-1`}),(0,E.jsx)(`span`,{children:`#111118`}),(0,E.jsx)(`span`,{children:`Elevated panels`})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`code`,{children:`accent`}),(0,E.jsx)(`span`,{className:`md-accent`,children:`#A69BFF`}),(0,E.jsx)(`span`,{children:`Interactive focus`})]})]})]})]})}function Bi(){return(0,E.jsx)(zi,{})}var Vi=[1,2,3,4,5,6,7,8];function Hi({fileName:e=`theme.ts`,language:t=`TypeScript`}){return(0,E.jsxs)(`div`,{className:`editor-card`,children:[(0,E.jsxs)(`div`,{className:`editor-tabs`,children:[(0,E.jsx)(`span`,{className:`editor-dot`}),(0,E.jsx)(`span`,{className:`editor-tab active`,children:e}),(0,E.jsx)(`span`,{className:`editor-tab`,children:`tokens.css`}),(0,E.jsx)(`span`,{className:`editor-actions`,children:`● ● ●`})]}),(0,E.jsxs)(`div`,{className:`editor-body`,children:[(0,E.jsx)(`div`,{className:`line-numbers`,children:Vi.map(e=>(0,E.jsxs)(`span`,{children:[e,(0,E.jsx)(`br`,{})]},e))}),(0,E.jsx)(`pre`,{children:(0,E.jsxs)(`code`,{children:[(0,E.jsx)(`span`,{className:`cm-comment`,children:`// A calm editor for focused work`}),`
`,(0,E.jsx)(`span`,{className:`cm-keyword`,children:`export const`}),` `,(0,E.jsx)(`span`,{className:`cm-variable`,children:`theme`}),` = `,`{`,`
`,` `,(0,E.jsx)(`span`,{className:`cm-property`,children:`name`}),`: `,(0,E.jsx)(`span`,{className:`cm-string`,children:`'northstar-dark'`}),`,`,`
`,` `,(0,E.jsx)(`span`,{className:`cm-property`,children:`colors`}),`: `,`{`,`
`,` `,(0,E.jsx)(`span`,{className:`cm-property`,children:`accent`}),`: `,(0,E.jsx)(`span`,{className:`cm-string`,children:`'#a69bff'`}),`,`,`
`,` `,(0,E.jsx)(`span`,{className:`cm-property`,children:`keyword`}),`: `,(0,E.jsx)(`span`,{className:`cm-string`,children:`'#e58bff'`}),`,`,`
`,` `,(0,E.jsx)(`span`,{className:`cm-property`,children:`comment`}),`: `,(0,E.jsx)(`span`,{className:`cm-string`,children:`'#666572'`}),`
`,` `,`}`,`
`,`}`]})})]}),(0,E.jsxs)(`div`,{className:`editor-status`,children:[(0,E.jsx)(`span`,{children:`Ln 4, Col 18`}),(0,E.jsxs)(`span`,{children:[t,` · UTF-8`]}),(0,E.jsxs)(`span`,{className:`editor-ready`,children:[(0,E.jsx)(j,{name:`check`,size:12}),` No issues`]})]})]})}function Ui(){return(0,E.jsx)(Hi,{})}function Wi(e){let t=e??window.location.pathname+window.location.search;window.location.href=`/api/auth/login?returnTo=${encodeURIComponent(t)}`}function Gi(){let[e,t]=(0,_.useState)(`loading`),[n,r]=(0,_.useState)(null);return(0,_.useEffect)(()=>{let e=!0;return fetch(`/api/auth/session`).then(e=>e.ok?e.json():{authenticated:!1,user:null}).then(n=>{e&&(n.authenticated&&n.user?(r(n.user),t(`authenticated`)):(r(null),t(`anonymous`)))}).catch(()=>{e&&(r(null),t(`anonymous`))}),()=>{e=!1}},[]),{status:e,user:n,login:Wi,signOut:(0,_.useCallback)(async()=>{try{await fetch(`/api/auth/logout`,{method:`POST`})}catch{}r(null),t(`anonymous`)},[])}}var Ki=class extends Error{status;code;constructor(e,t,n){super(n),this.name=`ApiError`,this.status=e,this.code=t}},qi=``;async function Ji(e,t){let n;try{n=await fetch(`${qi}${e}`,{...t,headers:{"Content-Type":`application/json`,...t?.headers}})}catch(e){throw new Ki(0,`network`,`Could not reach the API (${e instanceof Error?e.message:String(e)}). Is the Go server running?`)}if(n.status===204)return;let r=await n.json().catch(()=>null);if(!n.ok){let e=r?.error;throw new Ki(n.status,e?.code??`unknown`,e?.message??n.statusText)}return r}var Yi=Object.prototype.hasOwnProperty;function Xi(e,t){var n,r;if(e===t)return!0;if(e&&t&&(n=e.constructor)===t.constructor){if(n===Date)return e.getTime()===t.getTime();if(n===RegExp)return e.toString()===t.toString();if(n===Array){if((r=e.length)===t.length)for(;r--&&Xi(e[r],t[r]););return r===-1}if(!n||typeof e==`object`){for(n in r=0,e)if(Yi.call(e,n)&&++r&&!Yi.call(t,n)||!(n in t)||!Xi(e[n],t[n]))return!1;return Object.keys(t).length===r}}return e!==e&&t!==t}var Zi=new WeakMap,Qi=()=>{},$i=Object,N=e=>e===void 0,P=e=>typeof e==`function`,ea=(e,t)=>({...e,...t}),ta=e=>P(e.then),na={},ra={},ia=`undefined`,aa=typeof window!=ia,oa=typeof document!=ia,sa=aa&&`Deno`in window,ca=()=>aa&&typeof window.requestAnimationFrame!=ia,F=(e,t)=>{let n=Zi.get(e);return[()=>!N(t)&&e.get(t)||na,r=>{if(!N(t)){let i=e.get(t);t in ra||(ra[t]=i),n[5](t,ea(i,r),i||na)}},n[6],()=>!N(t)&&t in ra?ra[t]:!N(t)&&e.get(t)||na]},I=!0,la=()=>I,[ua,da]=aa&&window.addEventListener?[window.addEventListener.bind(window),window.removeEventListener.bind(window)]:[Qi,Qi],fa=()=>{let e=oa&&document.visibilityState;return N(e)||e!==`hidden`},pa=e=>(oa&&document.addEventListener(`visibilitychange`,e),ua(`focus`,e),()=>{oa&&document.removeEventListener(`visibilitychange`,e),da(`focus`,e)}),ma=e=>{let t=()=>{I=!0,e()},n=()=>{I=!1};return ua(`online`,t),ua(`offline`,n),()=>{da(`online`,t),da(`offline`,n)}},ha={isOnline:la,isVisible:fa},ga={initFocus:pa,initReconnect:ma},_a=!_.useId,va=!aa||sa,ya=e=>ca()?window.requestAnimationFrame(e):setTimeout(e,1),ba=va?_.useEffect:_.useLayoutEffect,xa=typeof navigator<`u`&&navigator.connection,Sa=!va&&xa&&([`slow-2g`,`2g`].includes(xa.effectiveType)||xa.saveData),Ca=new WeakMap,wa=e=>$i.prototype.toString.call(e),Ta=(e,t)=>e===`[object ${t}]`,Ea=0,Da=e=>{let t=typeof e,n=wa(e),r=Ta(n,`Date`),i=Ta(n,`RegExp`),a=Ta(n,`Object`),o,s;if($i(e)===e&&!r&&!i){if(o=Ca.get(e),o)return o;if(o=++Ea+`~`,Ca.set(e,o),Array.isArray(e)){for(o=`@`,s=0;s<e.length;s++)o+=Da(e[s])+`,`;Ca.set(e,o)}if(a){o=`#`;let t=$i.keys(e).sort();for(;!N(s=t.pop());)N(e[s])||(o+=s+`:`+Da(e[s])+`,`);Ca.set(e,o)}}else o=r?e.toJSON():t==`symbol`?e.toString():t==`string`?JSON.stringify(e):``+e;return o},Oa=e=>{if(P(e))try{e=e()}catch{e=``}let t=e;return e=typeof e==`string`?e:(Array.isArray(e)?e.length:e)?Da(e):``,[e,t]},ka=0,Aa=()=>++ka;async function ja(...e){let[t,n,r,i]=e,a=ea({populateCache:!0,throwOnError:!0},typeof i==`boolean`?{revalidate:i}:i||{}),o=a.populateCache,s=a.rollbackOnError,c=a.optimisticData,l=e=>typeof s==`function`?s(e):s!==!1,u=a.throwOnError;if(P(n)){let e=n,r=[],i=t.keys();for(let n of i)!/^\$(inf|sub)\$/.test(n)&&e(t.get(n)._k)&&r.push(n);return Promise.all(r.map(d))}return d(n);async function d(n){let[i]=Oa(n);if(!i)return;let[s,d]=F(t,i),[f,p,m,h]=Zi.get(t),g=()=>{let e=f[i];return(P(a.revalidate)?a.revalidate(s().data,n):a.revalidate!==!1)&&(delete m[i],delete h[i],e&&e[0])?e[0](2).then(()=>s().data):s().data};if(e.length<3)return g();let _=r,v,y=!1,b=Aa();p[i]=[b,0];let ee=!N(c),te=s(),ne=te.data,re=te._c,x=N(re)?ne:re;if(ee&&(c=P(c)?c(x,ne):c,d({data:c,_c:x})),P(_))try{_=_(x)}catch(e){v=e,y=!0}if(_&&ta(_)){if(_=await _.catch(e=>{v=e,y=!0}),b!==p[i][0]){if(y)throw v;return _}y&&ee&&l(v)&&(o=!0,d({data:x,_c:void 0}))}if(o&&(y||(P(o)?d({data:o(_,x),error:void 0,_c:void 0}):d({data:_,error:void 0,_c:void 0}))),p[i][1]=Aa(),Promise.resolve(g()).then(()=>{d({_c:void 0})}),y){if(u)throw v;return}return _}}var Ma=(e,t)=>{for(let n in e)e[n][0]&&e[n][0](t)},Na= Line truncated
`)).blocks}function ko(e){let t=[],n=0;outer:for(;n<e.length;){let r=e[n];if(r===void 0)break;if(r.trim()===``){n++;continue}let i=Co.exec(r);if(i){let r=i[1]??"`",a=i[2]??``,o=[];for(n++;n<e.length;){let t=e[n];if(t===void 0||RegExp(`^ {0,3}${r[0]}{${r.length},}\\s*$`).test(t))break;o.push(t),n++}n++,t.push({kind:`code`,language:a,text:o.join(`
`)});continue}let a=xo.exec(r);if(a){t.push({kind:`heading`,level:a[1]?.length??1,spans:Po(a[2]??``)}),n++;continue}if(So.test(r)){t.push({kind:`hr`}),n++;continue}let o=wo.exec(r);if(o){let r=[o[1]??``];for(n++;n<e.length;){let t=wo.exec(e[n]??``);if(!t)break;r.push(t[1]??``),n++}t.push({kind:`quote`,blocks:ko(r).blocks});continue}let s=To.exec(r);if(s){let r=Ao(e,n,s[1]?.length??0);t.push(r.list),n=r.next;continue}let c=e[n+1];if(Eo.test(r)&&c!==void 0&&Do.test(c)){let i=Mo(r);n+=2;let a=[];for(;n<e.length;){let t=e[n];if(t===void 0||!Eo.test(t)||t.trim()===``)break;a.push(Mo(t)),n++}t.push({kind:`table`,head:i,rows:a});continue}let l=[r];for(n++;n<e.length;){let t=e[n];if(t===void 0||t.trim()===``||xo.test(t)||So.test(t)||Co.test(t)||wo.test(t)||To.test(t))break;l.push(t),n++}if(t.push({kind:`paragraph`,spans:Po(l.join(` `).trim())}),t.length>5e3)break outer}return{blocks:t,next:n}}function Ao(e,t,n){let r=To.exec(e[t]??``)?.[2]??``,i=/\d/.test(r),a=i&&parseInt(r,10)||1,o=[],s=t;for(;s<e.length;){let t=e[s];if(t===void 0)break;if(t.trim()===``){if(s+1<e.length&&jo(e[s+1]??``,n)){s++;continue}break}let r=To.exec(t);if(!r)break;let a=r[1]?.length??0;if(a<n)break;if(a>n){if(o.length>0){let t=Ao(e,s,a),n=o[o.length-1];n&&(n.sub=t.list),s=t.next;continue}break}let c=r[2]??``;if(i!==/\d/.test(c))break;o.push({spans:Po(r[3]??``)}),s++}return{list:{kind:`list`,ordered:i,start:a,items:o},next:s}}function jo(e,t){let n=To.exec(e);return n!==null&&(n[1]?.length??0)===t}function Mo(e){let t=e.trim();return t.startsWith(`|`)&&(t=t.slice(1)),t.endsWith(`|`)&&(t=t.slice(0,-1)),t.split(`|`).map(e=>e.trim())}var No=/(!\[[^\]]*\]\([^)]*\))|(\[[^\]]*\]\([^)]*\))|(`[^`\n]+`)|(\*\*[^*\n]+\*\*)|(__[^_\n]+__)|(~~[^~\n]+~~)|(\*[^*\n]+\*)|(_[^_\n]+_)/g;function Po(e){let t=[],n=0;for(let r of e.matchAll(No)){let i=r.index??0;i>n&&t.push({kind:`text`,text:e.slice(n,i)}),t.push(...Fo(r[0])),n=i+r[0].length}return n<e.length&&t.push({kind:`text`,text:e.slice(n)}),t}function Fo(e){if(e.startsWith(`![`)){let t=/^\!\[([^\]]*)\]\(([^)\s]*)(?:\s+"[^"]*")?\)$/.exec(e);if(t)return[{kind:`image`,alt:t[1]??``,src:t[2]??``}]}if(e.startsWith(`[`)){let t=/^\[([^\]]*)\]\(([^)\s]*)(?:\s+"[^"]*")?\)$/.exec(e);if(t)return[{kind:`link`,href:t[2]??``,spans:Po(t[1]??``)}]}return e.startsWith("`")?[{kind:`code`,text:e.slice(1,-1)}]:e.startsWith(`~~`)?[{kind:`del`,spans:Po(e.slice(2,-2))}]:e.startsWith(`**`)||e.startsWith(`__`)?[{kind:`strong`,spans:Po(e.slice(2,-2))}]:[{kind:`em`,spans:Po(e.slice(1,-1))}]}function Io({source:e,onNavigate:t,className:n}){let r=(0,_.useMemo)(()=>Oo(e),[e]);return(0,E.jsx)(`div`,{className:n,children:r.map((e,n)=>(0,E.jsx)(Lo,{block:e,onNavigate:t},n))})}function Lo({block:e,onNavigate:t}){switch(e.kind){case`heading`:{let n=`h${e.level}`;return(0,E.jsx)(n,{children:e.spans.map((e,n)=>(0,E.jsx)(zo,{span:e,onNavigate:t},n))})}case`paragraph`:return(0,E.jsx)(`p`,{children:e.spans.map((e,n)=>(0,E.jsx)(zo,{span:e,onNavigate:t},n))});case`code`:return(0,E.jsx)(`pre`,{children:(0,E.jsx)(`code`,{children:e.text})});case`quote`:return(0,E.jsx)(`blockquote`,{children:e.blocks.map((e,n)=>(0,E.jsx)(Lo,{block:e,onNavigate:t},n))});case`list`:return(0,E.jsx)(Ro,{list:e,onNavigate:t});case`table`:return(0,E.jsxs)(`table`,{children:[(0,E.jsx)(`thead`,{children:(0,E.jsx)(`tr`,{children:e.head.map((e,t)=>(0,E.jsx)(`th`,{children:e},t))})}),(0,E.jsx)(`tbody`,{children:e.rows.map((e,t)=>(0,E.jsx)(`tr`,{children:e.map((e,t)=>(0,E.jsx)(`td`,{children:e},t))},t))})]});case`hr`:return(0,E.jsx)(`hr`,{})}}function Ro({list:e,onNavigate:t}){let n=e.ordered?`ol`:`ul`;return(0,E.jsx)(n,{start:e.ordered&&e.start!==1?e.start:void 0,children:e.items.map((e,n)=>(0,E.jsxs)(`li`,{children:[e.spans.map((e,n)=>(0,E.jsx)(zo,{span:e,onNavigate:t},n)),e.sub&&(0,E.jsx)(Ro,{list:e.sub,onNavigate:t})]},n))})}function zo({span:e,onNavigate:t}){switch(e.kind){case`text`:return(0,E.jsx)(E.Fragment,{children:e.text});case`code`:return(0,E.jsx)(`code`,{children:e.text});case`strong`:return(0,E.jsx)(`strong`,{children:e.spans.map((e,n)=>(0,E.jsx)(zo,{span:e,onNavigate:t},n))});case`em`:return(0,E.jsx)(`em`,{children:e.spans.map((e,n)=>(0,E.jsx)(zo,{span:e,onNavigate:t},n))});case`del`:return(0,E.jsx)(`del`,{children:e.spans.map((e,n)=>(0,E.jsx)(zo,{span:e,onNavigate:t},n))});case`image`:return(0,E.jsx)(`img`,{src:e.src,alt:e.alt,loading:`lazy`});case`link`:return(0,E.jsx)(Bo,{href:e.href,onNavigate:t,children:Vo(e.spans,t)})}}function Bo({href:e,onNavigate:t,children:n,...r}){let i=/^https?:\/\//i.test(e);return(0,E.jsx)(`a`,{href:e,onClick:n=>{i&&t&&(n.preventDefault(),t(e))},title:i?`Open in the agent view`:void 0,...r,children:n})}function Vo(e,t){return e.map((e,n)=>(0,E.jsx)(zo,{span:e,onNavigate:t},n))}function Ho(e){return Ji(`/api/v1/browse?url=${encodeURIComponent(e)}`)}var Uo={snapshot:e=>[`browse`,`snapshot`,e]};function Wo(e){let{data:t,error:n,isLoading:r,mutate:i}=ao(e===null?null:Uo.snapshot(e),()=>Ho(e),{keepPreviousData:!0});return{snapshot:t,error:nLine truncated
`,e.message]}):this.props.children}};(0,v.createRoot)(document.getElementById(`root`)).render((0,E.jsx)(_.StrictMode,{children:(0,E.jsx)(Ms,{children:(0,E.jsx)(Xe,{children:(0,E.jsx)(js,{})})})}));
-1
View File
@@ -1 +0,0 @@
@import "https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Manrope:wght@400;500;600;700;800&display=swap";:root{--root-font-size:112%;--text-3xs:.5625rem;--text-2xs:.625rem;--text-xs:.6875rem;--text-sm:.75rem;--text-md:.875rem;--text-base:1rem;--text-lg:1.3125rem;--text-xl:1.5625rem;--text-2xl:1.75rem;--text-hero:clamp(2.625rem, 5vw, 4.375rem);--font-sans:"Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-mono:"DM Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;--space-1:.25rem;--space-2:.5rem;--space-3:.75rem;--space-4:1rem;--space-5:1.25rem;--space-6:1.5rem;--space-7:2rem;--space-8:2.5rem;--space-9:3rem;--space-10:4rem;--space-11:5rem;--radius-sm:5px;--radius-md:6px;--radius-lg:9px;--radius-xl:12px;--radius-2xl:14px;--radius-pill:9999px;--focus-ring:2px solid var(--ring);--focus-offset:2px}:root,:root[data-theme=light],.light{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--background:lab(100% 0 0);--foreground:lab(2.75381% 0 0);--card:lab(97.68% -.0000298023 .0000119209);--card-foreground:lab(2.75381% 0 0);--popover:lab(100% 0 0);--popover-foreground:lab(2.75381% 0 0);--primary:lab(7.78201% -.0000149012 0);--primary-foreground:lab(98.26% 0 0);--secondary:lab(94.2% 0 0);--secondary-foreground:lab(7.78201% -.0000149012 0);--muted:lab(94.2% 0 0);--muted-foreground:lab(39.68% 0 0);--accent:lab(94.2% 0 0);--accent-foreground:lab(7.78201% -.0000149012 0);--destructive:lab(48.4493% 77.4328 61.5452);--destructive-foreground:lab(98.26% 0 0);--border:lab(86.08% -.0000298023 0);--input:lab(86.08% -.0000298023 0);--ring:lab(42% 0 0);--chart-1:lab(57.1026% 64.2584 89.8886);--chart-2:lab(55.0223% -41.0774 -3.90277);--chart-3:lab(30.372% -13.1853 -18.7887);--chart-4:lab(80.1641% 16.6016 99.2089);--chart-5:lab(72.7183% 31.8672 97.9407);--sidebar:lab(96.52% -.0000298023 .0000119209);--sidebar-foreground:lab(2.75381% 0 0);--sidebar-primary:lab(7.78201% -.0000149012 0);--sidebar-primary-foreground:lab(98.26% 0 0);--sidebar-accent:lab(94.2% 0 0);--sidebar-accent-foreground:lab(7.78201% -.0000149012 0);--sidebar-border:lab(86.08% -.0000298023 0);--sidebar-ring:lab(42% 0 0);--dot-color:lab(7.78201% -.0000149012 0/.05)}:root:not([data-theme=light]),:root[data-theme=dark],.dark{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--background:lab(0% 0 0);--foreground:lab(94.78% 0 0);--card:lab(.903296% 0 0);--card-foreground:lab(94.78% 0 0);--popover:lab(.903296% 0 0);--popover-foreground:lab(94.78% 0 0);--primary:lab(89.9321% -47.9163 -15.7303);--primary-foreground:lab(0% 0 0);--secondary:lab(2.47865% 0 0);--secondary-foreground:lab(94.78% 0 0);--muted:lab(2.47865% 0 0);--muted-foreground:lab(60.56% 0 0);--accent:lab(1.98454% 0 0);--accent-foreground:lab(89.9321% -47.9163 -15.7303);--destructive:lab(54.5811% 71.684 50.9279);--destructive-foreground:lab(100% 0 0);--border:lab(4.8411% 0 0);--input:lab(1.98454% 0 0);--ring:lab(89.9321% -47.9163 -15.7303);--chart-1:lab(89.9321% -47.9163 -15.7303);--chart-2:lab(66.9756% -58.27 19.5419);--chart-3:lab(72.7183% 31.8672 97.9407);--chart-4:lab(52.0183% 66.11 -78.2316);--chart-5:lab(56.101% 79.4328 31.4532);--sidebar:lab(.498049% 0 0);--sidebar-foreground:lab(94.78% 0 0);--sidebar-primary:lab(89.9321% -47.9163 -15.7303);--sidebar-primary-foreground:lab(0% 0 0);--sidebar-accent:lab(1.98454% 0 0);--sidebar-accent-foreground:lab(89.9321% -47.9163 -15.7303);--sidebar-border:lab(4.8411% 0 0);--sidebar-ring:lab(89.9321% -47.9163 -15.7303);--dot-color:lab(89.9321% -47.9163 -15.7303/.045)}html{font-size:var(--root-font-size)}.dot-canvas{background-image:radial-gradient(var(--dot-color) 1px, transparent 1px);background-size:24px 24px}.dot-canvas-dense{background-image:radial-gradient(var(--dot-color) 1px, transparent 1px);background-size:16px 16px}.flex-center{justify-content:center;align-items:center;display:flex}.flex-between{justify-content:space-between;align-items:center;display:flex}.flex-col{flex-direction:column;display:flex}.stack-xs{gap:var(--space-1);flex-direction:column;display:flex}.stack-sm{gap:var(--space-2);flex-direction:column;display:flex}.stack-md{gap:var(--space-4);flex-direction:column;display:flex}.stack-lg{gap:var(--space-6);flex-direction:column;display:flex}.row-xs{align-items:center;gap:var(--space-1);display:flex}.row-sm{align-items:center;gap:var(--space-2);display:flex}.row-md{align-items:center;gap:var(--space-4);display:flex}.grid-2{gap:var(--space-4);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.grid-3{gap:var(--space-4);grid-template-columns:repeat(3,minmax(0,1fr));display:grid}.grid-4{gap:var(--space-4);grid-template-columns:repeat(4,minmax(0,1fr));display:grid}@media (width<=760px){.grid-2,.grid-3,.grid-4{grid-template-columns:1fr}}.surface-card{background:var(--card);color:var(--card-foreground);border:1px solid var(--border);border-radius:var(--radius-lg);padding:var(--space-5);box-shadow:0 4px 20px #0000001f}.surface-sidebar{backLine truncated
-25
View File
@@ -1,25 +0,0 @@
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,o)=>(o=n==null?{}:e(i(n)),s(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var l=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.for(`react.view_transition`),m=Symbol.iterator;function h(e){return typeof e!=`object`||!e?null:(e=m&&e[m]||e[`@@iterator`],typeof e==`function`?e:null)}var g={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},_=Object.assign,v={};function y(e,t,n){this.props=e,this.context=t,this.refs=v,this.updater=n||g}y.prototype.isReactComponent={},y.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},y.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function b(){}b.prototype=y.prototype;function ee(e,t,n){this.props=e,this.context=t,this.refs=v,this.updater=n||g}var te=ee.prototype=new b;te.constructor=ee,_(te,y.prototype),te.isPureReactComponent=!0;var ne=Array.isArray;function re(){}var x={H:null,A:null,T:null,S:null},ie=Object.prototype.hasOwnProperty;function S(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function ae(e,t){return S(e.type,t,e.props)}function C(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function oe(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var se=/\/+/g;function ce(e,t){return typeof e==`object`&&e&&e.key!=null?oe(``+e.key):t.toString(36)}function le(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(re,re):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function ue(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,ue(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+ce(e,0):a,ne(o)?(i=``,c!=null&&(i=c.replace(se,`$&/`)+`/`),ue(o,r,i,``,function(e){return e})):o!=null&&(C(o)&&(o=ae(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(se,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(ne(e))for(var u=0;u<e.length;u++)a=e[u],s=l+ce(a,u),c+=ue(a,r,i,s,o);else if(u=h(e),typeof u==`function`)for(e=u.call(e),u=0;!(a=e.next()).done;)a=a.value,s=l+ce(a,u++),c+=ue(a,r,i,s,o);else if(s===`object`){if(typeof e.then==`function`)return ue(le(e),r,i,a,o);throw r=String(e),Error(`Objects are not valid as a React child (found: `+(r===`[object Object]`?`object with keys {`+Object.keys(e).join(`, `)+`}`:r)+`). If you meant to render a collection of children, use an array instead.`)}return c}function de(e,t,n){if(e==null)return e;var r=[],i=0;return ue(e,r,``,``,function(e){return t.call(n,e,i++)}),r}function fe(e){if(e._status===-1){var t=e._result,n=t();n.then(function(t){(e._status===0||e._status===-1)&&(e._status=1,e._result=t,n.status===void 0&&(n.status=`fulfilled`,n.value=t))},function(t){(e._status===0||e._status===-1)&&(e._status=2,e._result=t,n.status===void 0&&(n.status=`rejected`,n.reason=t))}),e._status===-1&&(e._status=Line truncated
at`)?` (<anonymous>)`:-1<e.stack.indexOf(`@`)?`@unknown:0:0`:``}return`
`+Be+e+Ve}var Ue=!1;function D(e,t){if(!e||Ue)return``;Ue=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var r={DetermineComponentFrameRoot:function(){try{if(t){var n=function(){throw Error()};if(Object.defineProperty(n.prototype,"props",{set:function(){throw Error()}}),typeof Reflect==`object`&&Reflect.construct){try{Reflect.construct(n,[])}catch(e){var r=e}Reflect.construct(e,[],n)}else{try{n.call()}catch(e){r=e}n=!1;try{var i=Object.getOwnPropertyDescriptor(e.prototype,`props`);Object.defineProperty(e.prototype,"props",{configurable:!0,set:function(){throw Error()}}),n=!0,new e}finally{n&&(i===void 0?delete e.prototype.props:Object.defineProperty(e.prototype,"props",i))}}}else{try{throw Error()}catch(e){r=e}(n=e())&&typeof n.catch==`function`&&n.catch(function(){})}}catch(e){if(e&&r&&typeof e.stack==`string`)return[e.stack,r.stack]}return[null,null]}};r.DetermineComponentFrameRoot.displayName=`DetermineComponentFrameRoot`;var i=Object.getOwnPropertyDescriptor(r.DetermineComponentFrameRoot,`name`);i&&i.configurable&&Object.defineProperty(r.DetermineComponentFrameRoot,"name",{value:`DetermineComponentFrameRoot`});var a=r.DetermineComponentFrameRoot(),o=a[0],s=a[1];if(o&&s){var c=o.split(`
`),l=s.split(`
`);for(i=r=0;r<c.length&&!c[r].includes(`DetermineComponentFrameRoot`);)r++;for(;i<l.length&&!l[i].includes(`DetermineComponentFrameRoot`);)i++;if(r===c.length||i===l.length)for(r=c.length-1,i=l.length-1;1<=r&&0<=i&&c[r]!==l[i];)i--;for(;1<=r&&0<=i;r--,i--)if(c[r]!==l[i]){if(r!==1||i!==1)do if(r--,i--,0>i||c[r]!==l[i]){var u=`
`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(`<anonymous>`)&&(u=u.replace(`<anonymous>`,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{Ue=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?He(n):``}function We(e,t){switch(e.tag){case 26:case 27:case 5:return He(e.type);case 16:return He(`Lazy`);case 13:return e.child!==t&&t!==null?He(`Suspense Fallback`):He(`Suspense`);case 19:return He(`SuspenseList`);case 0:case 15:return D(e.type,!1);case 11:return D(e.type.render,!1);case 1:return D(e.type,!0);case 31:return He(`Activity`);case 30:return He(`ViewTransition`);default:return``}}function Ge(e){try{var t=``,n=null;do t+=We(e,n),n=e,e=e.return;while(e);return t}catch(e){return`
Error generating stack: `+e.message+`
`+e.stack}}var Ke=Object.prototype.hasOwnProperty,qe=t.unstable_scheduleCallback,Je=t.unstable_cancelCallback,Ye=t.unstable_shouldYield,Xe=t.unstable_requestPaint,O=t.unstable_now,Ze=t.unstable_getCurrentPriorityLevel,Qe=t.unstable_ImmediatePriority,$e=t.unstable_UserBlockingPriority,et=t.unstable_NormalPriority,tt=t.unstable_LowPriority,nt=t.unstable_IdlePriority,rt=t.log,it=t.unstable_setDisableYieldValue,at=null,ot=null;function st(e){if(typeof rt==`function`&&it(e),ot&&typeof ot.setStrictMode==`function`)try{ot.setStrictMode(at,e)}catch{}}var ct=Math.clz32?Math.clz32:dt,lt=Math.log,ut=Math.LN2;function dt(e){return e>>>=0,e===0?32:31-(lt(e)/ut|0)|0}var ft=256,pt=262144,mt=4194304;function ht(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&-e;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function gt(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=ht(n))):i=ht(o):i=ht(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=ht(n))):i=ht(o)):i=ht(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function _t(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function vt(e,t){t&8&&(t|=t&32);var n=e.entangledLanes;if(n!==0)for(e=e.entanglements,n&=t;0<n;){var r=31-ct(n),i=1<<r;t|=e[r],n&=~i}return t}function yt(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function k(){var e=mt;return mt<<=1,!(mt&62914560)&&(mt=4194304),e}function bt(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function xt(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function St(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0<n;){var u=31-ct(n),d=1<<u;s[u]=0,c[u]=-1;var f=l[u];if(f!==null)for(l[u]=null,u=0;u<f.length;u++){var p=f[u];p!==null&&(p.lane&=-536870913)}n&=~d}r!==0&&Ct(e,r,0),a!==0&&i===0&&e.tag!==0&&(e.suspendedLanes|=a&~(o&~t))}function Ct(e,t,n){e.pendingLanes|=t,e.suspendedLanes&=~t;var r=31-ct(t);e.entangledLanes|=t,e.entanglements[r]=e.entanglements[r]|1073741824|n&261930}function wt(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-ct(n),i=1<<r;i&t|e[r]&t&&(e[r]|=t),n&=~i}}function Tt(e,t){var n=t&-t;return n=n&42?1:Et(n),(n&(e.suspendedLanes|t))===0?n:0}function Et(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:e=128;break;case 268435456:e=134217728;break;default:e=0}return e}function Dt(e){return e&=-e,2<e?8<e?e&134217727?32:268435456:8:2}function Ot(){var e=T.p;return e===0?(e=window.event,e===void 0?32:Ch(e.type)):e}function kt(e,t){var n=T.p;try{return T.p=e,t()}finally{T.p=n}}var At=Math.random().toString(36).slice(2),jt=`__reactFiber$`+At,Mt=`__reactProps$`+At,Nt=`__reactContainer$`+At,Pt=`__reactEvents$`+At,Ft=`__reactListeners$`+At,It=`__reactHandles$`+At,Lt=`__reactResources$`+At,Rt=`__reactMarker$`+At,zt=`__reactLoad$`+At;function Bt(e){delete e[jt],delete e[Mt],delete e[Ft],delete e[It]}function Vt(e){var t;if(t=e[jt])return t;for(var n=e.parentNode;n;){if(t=n[Nt]||n[jt]){if(n=t.alternate,t.child!==null||n!==null&&n.child!==null)for(e=fm(e);e!==null;){if(n=e[jt])return n;e=fm(e)}return t}e=n,n=e.parentNode}return null}function Ht(e){if(e=e[jt]||e[Nt]){var t=e.tag;if(t===5||t===6||t===13||t===31||t===26||t===27||t===3)return e}return null}function Ut(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e.stateNode;throw Error(i(33))}function Wt(e){var t=e[Lt];return t||=e[Lt]={hoistableStyles:new Map,hoistableScripts:new Map},t}function Gt(e){e[Rt]=!0}function Kt(e){e[zt]=void 0}var qt=new Set,Jt={};function Yt(e,t){Xt(e,t),Xt(e+`Capture`,t)}function Xt(e,t){for(Jt[e]=t,e=0;e<t.leng Line truncated
`).replace(Qf,``)}function ep(e,t){return t=$f(t),$f(e)===t}function $(e,t,n,r,a,o){switch(n){case`children`:if(typeof r==`string`)t===`body`||t===`textarea`&&r===``||yn(e,r);else if(typeof r==`number`||typeof r==`bigint`)t!==`body`&&yn(e,``+r);else return;break;case`className`:rn(e,`class`,r);break;case`tabIndex`:rn(e,`tabindex`,r);break;case`dir`:case`role`:case`viewBox`:case`width`:case`height`:rn(e,n,r);break;case`style`:Sn(e,r,o);return;case`data`:if(t!==`object`){rn(e,`data`,r);break}case`src`:case`href`:if(r===``&&(t!==`a`||n!==`href`)){e.removeAttribute(n);break}if(r==null||typeof r==`function`||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=En(r),e.setAttribute(n,r);break;case`action`:case`formAction`:if(typeof r==`function`){e.setAttribute(n,`javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')`);break}if(typeof o==`function`&&(n===`formAction`?(t!==`input`&&$(e,t,`name`,a.name,a,null),$(e,t,`formEncType`,a.formEncType,a,null),$(e,t,`formMethod`,a.formMethod,a,null),$(e,t,`formTarget`,a.formTarget,a,null)):($(e,t,`encType`,a.encType,a,null),$(e,t,`method`,a.method,a,null),$(e,t,`target`,a.target,a,null))),r==null||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=En(r),e.setAttribute(n,r);break;case`onClick`:r!=null&&(e.onclick=Dn);return;case`onScroll`:r!=null&&Q(`scroll`,e);return;case`onScrollEnd`:r!=null&&Q(`scrollend`,e);return;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));o?.__html!==n&&(e.innerHTML=n)}}break;case`multiple`:e.multiple=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`muted`:e.muted=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`suppressContentEditableWarning`:case`suppressHydrationWarning`:case`defaultValue`:case`defaultChecked`:case`innerHTML`:case`ref`:break;case`autoFocus`:break;case`xlinkHref`:if(r==null||typeof r==`function`||typeof r==`boolean`||typeof r==`symbol`){e.removeAttribute(`xlink:href`);break}n=En(r),e.setAttributeNS(`http://www.w3.org/1999/xlink`,`xlink:href`,n);break;case`contentEditable`:case`spellCheck`:case`draggable`:case`value`:case`autoReverse`:case`externalResourcesRequired`:case`focusable`:case`preserveAlpha`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,r):e.removeAttribute(n);break;case`inert`:case`allowFullScreen`:case`async`:case`autoPlay`:case`controls`:case`credentialless`:case`default`:case`defer`:case`disabled`:case`disablePictureInPicture`:case`disableRemotePlayback`:case`formNoValidate`:case`hidden`:case`loop`:case`noModule`:case`noValidate`:case`open`:case`playsInline`:case`readOnly`:case`required`:case`reversed`:case`scoped`:case`seamless`:case`itemScope`:r&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,``):e.removeAttribute(n);break;case`capture`:case`download`:!0===r?e.setAttribute(n,``):!1!==r&&r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,r):e.removeAttribute(n);break;case`cols`:case`rows`:case`size`:case`span`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`&&!isNaN(r)&&1<=r?e.setAttribute(n,r):e.removeAttribute(n);break;case`rowSpan`:case`start`:r==null||typeof r==`function`||typeof r==`symbol`||isNaN(r)?e.removeAttribute(n):e.setAttribute(n,r);break;case`popover`:Q(`beforetoggle`,e),Q(`toggle`,e),nn(e,`popover`,r);break;case`xlinkActuate`:an(e,`http://www.w3.org/1999/xlink`,`xlink:actuate`,r);break;case`xlinkArcrole`:an(e,`http://www.w3.org/1999/xlink`,`xlink:arcrole`,r);break;case`xlinkRole`:an(e,`http://www.w3.org/1999/xlink`,`xlink:role`,r);break;case`xlinkShow`:an(e,`http://www.w3.org/1999/xlink`,`xlink:show`,r);break;case`xlinkTitle`:an(e,`http://www.w3.org/1999/xlink`,`xlink:title`,r);break;case`xlinkType`:an(e,`http://www.w3.org/1999/xlink`,`xlink:type`,r);break;case`xmlBase`:an(e,`http://www.w3.org/XML/1998/namespace`,`xml:base`,r);break;case`xmlLang`:an(e,`http://www.w3.org/XML/1998/namespace`,`xml:lang`,r);break;case`xmlSpace`:an(e,`http://www.w3.org/XML/1998/namespace`,`xml:space`,r);break;case`is`:nn(e,`is`,r);break;case`innerText`:case`textContent`:return;default:if(!(2<n.length)||n[0]!==`o`&&n[0]!==`O`||n[1]!==`n`&&n[1]!==`N`)n=wn.get(n)||n,nn(e,n,r);else return}A=!0}function tp(e,t,n,r,a,o){switch(n){case`style`:Sn(e,r,o);return;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));o?.__html!==n&&(e.innerHTML=n)}}break;case`children`:if(typeof r==`string`)yn(e,r);else if(typeof r==`number`||typeof r==`bigint`)yn(e,``+r);else return;break;case`onScroll`:r!=null&&Q(`scroll`,e);return;case`onScrollEnd`:r!=null&&Q(`scrollend`,e);return;case`onClick`:r!=null&&(e.onclick=D Line truncated
export function `],[`blue`,`DeployAction`],[`plain`,`() {
return (
`],[`plain`,` <Button variant="primary">Deploy changes</Button>
)
}`]];function Ri(){let[e,t]=(0,_.useState)(!1),{schedule:n}=nr();return(0,D.jsxs)(`div`,{className:`code-card`,children:[(0,D.jsxs)(`div`,{className:`code-head`,children:[(0,D.jsx)(`span`,{className:`mono muted`,children:`button.tsx`}),(0,D.jsxs)(`button`,{className:`copy-button`,onClick:()=>{navigator.clipboard?.writeText(Ii),t(!0),n(()=>t(!1),1600)},children:[(0,D.jsx)(j,{name:e?`check`:`copy`,size:14}),e?`Copied`:`Copy code`]})]}),(0,D.jsx)(`pre`,{children:(0,D.jsx)(`code`,{children:Li.map(([e,t],n)=>e===`plain`?(0,D.jsx)(`span`,{children:t},n):(0,D.jsx)(`span`,{className:e,children:t},n))})})]})}function zi(){return(0,D.jsx)(Ri,{})}function Bi({title:e=`Quiet power, shipped weekly.`,kicker:t=`NORTHSTAR / RELEASE NOTES`}){return(0,D.jsxs)(`div`,{className:`markdown-card`,children:[(0,D.jsxs)(`div`,{className:`markdown-toolbar`,children:[(0,D.jsx)(`span`,{className:`eyebrow`,children:`Preview`}),(0,D.jsx)(`span`,{className:`mono muted`,children:`release-notes.md`})]}),(0,D.jsxs)(`article`,{className:`markdown-body`,children:[(0,D.jsx)(`span`,{className:`md-kicker`,children:t}),(0,D.jsx)(`h3`,{children:e}),(0,D.jsx)(`p`,{children:`Our components help teams move quickly without making the interface feel rushed.`}),(0,D.jsxs)(`div`,{className:`md-callout`,children:[(0,D.jsx)(`span`,{className:`callout-mark`,children:`✦`}),(0,D.jsxs)(`p`,{children:[(0,D.jsx)(`strong`,{children:`New in 0.8.2`}),(0,D.jsx)(`br`,{}),`Button focus rings now respect your theme while keeping a dependable contrast ratio.`]})]}),(0,D.jsx)(`h4`,{children:`What changed`}),(0,D.jsxs)(`ul`,{children:[(0,D.jsxs)(`li`,{children:[`Added `,(0,D.jsx)(`code`,{children:`accent-subtle`}),` surface token`]}),(0,D.jsx)(`li`,{children:`Improved keyboard navigation in menus`}),(0,D.jsx)(`li`,{children:`Reduced layout shift during loading states`})]}),(0,D.jsxs)(`div`,{className:`md-table`,children:[(0,D.jsxs)(`div`,{children:[(0,D.jsx)(`span`,{children:`Token`}),(0,D.jsx)(`span`,{children:`Value`}),(0,D.jsx)(`span`,{children:`Role`})]}),(0,D.jsxs)(`div`,{children:[(0,D.jsx)(`code`,{children:`surface-1`}),(0,D.jsx)(`span`,{children:`#111118`}),(0,D.jsx)(`span`,{children:`Elevated panels`})]}),(0,D.jsxs)(`div`,{children:[(0,D.jsx)(`code`,{children:`accent`}),(0,D.jsx)(`span`,{className:`md-accent`,children:`#A69BFF`}),(0,D.jsx)(`span`,{children:`Interactive focus`})]})]})]})]})}function Vi(){return(0,D.jsx)(Bi,{})}var Hi=[1,2,3,4,5,6,7,8];function Ui({fileName:e=`theme.ts`,language:t=`TypeScript`}){return(0,D.jsxs)(`div`,{className:`editor-card`,children:[(0,D.jsxs)(`div`,{className:`editor-tabs`,children:[(0,D.jsx)(`span`,{className:`editor-dot`}),(0,D.jsx)(`span`,{className:`editor-tab active`,children:e}),(0,D.jsx)(`span`,{className:`editor-tab`,children:`tokens.css`}),(0,D.jsx)(`span`,{className:`editor-actions`,children:`● ● ●`})]}),(0,D.jsxs)(`div`,{className:`editor-body`,children:[(0,D.jsx)(`div`,{className:`line-numbers`,children:Hi.map(e=>(0,D.jsxs)(`span`,{children:[e,(0,D.jsx)(`br`,{})]},e))}),(0,D.jsx)(`pre`,{children:(0,D.jsxs)(`code`,{children:[(0,D.jsx)(`span`,{className:`cm-comment`,children:`// A calm editor for focused work`}),`
`,(0,D.jsx)(`span`,{className:`cm-keyword`,children:`export const`}),` `,(0,D.jsx)(`span`,{className:`cm-variable`,children:`theme`}),` = `,`{`,`
`,` `,(0,D.jsx)(`span`,{className:`cm-property`,children:`name`}),`: `,(0,D.jsx)(`span`,{className:`cm-string`,children:`'northstar-dark'`}),`,`,`
`,` `,(0,D.jsx)(`span`,{className:`cm-property`,children:`colors`}),`: `,`{`,`
`,` `,(0,D.jsx)(`span`,{className:`cm-property`,children:`accent`}),`: `,(0,D.jsx)(`span`,{className:`cm-string`,children:`'#a69bff'`}),`,`,`
`,` `,(0,D.jsx)(`span`,{className:`cm-property`,children:`keyword`}),`: `,(0,D.jsx)(`span`,{className:`cm-string`,children:`'#e58bff'`}),`,`,`
`,` `,(0,D.jsx)(`span`,{className:`cm-property`,children:`comment`}),`: `,(0,D.jsx)(`span`,{className:`cm-string`,children:`'#666572'`}),`
`,` `,`}`,`
`,`}`]})})]}),(0,D.jsxs)(`div`,{className:`editor-status`,children:[(0,D.jsx)(`span`,{children:`Ln 4, Col 18`}),(0,D.jsxs)(`span`,{children:[t,` · UTF-8`]}),(0,D.jsxs)(`span`,{className:`editor-ready`,children:[(0,D.jsx)(j,{name:`check`,size:12}),` No issues`]})]})]})}function Wi(){return(0,D.jsx)(Ui,{})}function Gi(e){let t=e??window.location.pathname+window.location.search;window.location.href=`/api/auth/login?returnTo=${encodeURIComponent(t)}`}function Ki(){let[e,t]=(0,_.useState)(`loading`),[n,r]=(0,_.useState)(null);return(0,_.useEffect)(()=>{let e=!0;return fetch(`/api/auth/session`).then(e=>e.ok?e.json():{authenticated:!1,user:null}).then(n=>{e&&(n.authenticated&&n.user?(r(n.user),t(`authenticated`)):(r(null),t(`anonymous`)))}).catch(()=>{e&&(r(null),t(`anonymous`))}),()=>{e=!1}},[]),{status:e,user:n,login:Gi,signOut:(0,_.useCallback)(async()=>{try{await fetch(`/api/auth/logout`,{method:`POST`})}catch{}r(null),t(`anonymous`)},[])}}var qi=class extends Error{status;code;constructor(e,t,n){super(n),this.name=`ApiError`,this.status=e,this.code=t}},Ji=``;async function Yi(e,t){let n;try{n=await fetch(`${Ji}${e}`,{...t,headers:{"Content-Type":`application/json`,...t?.headers}})}catch(e){throw new qi(0,`network`,`Could not reach the API (${e instanceof Error?e.message:String(e)}). Is the Go server running?`)}if(n.status===204)return;let r=await n.json().catch(()=>null);if(!n.ok){let e=r?.error;throw new qi(n.status,e?.code??`unknown`,e?.message??n.statusText)}return r}var Xi=Object.prototype.hasOwnProperty;function Zi(e,t){var n,r;if(e===t)return!0;if(e&&t&&(n=e.constructor)===t.constructor){if(n===Date)return e.getTime()===t.getTime();if(n===RegExp)return e.toString()===t.toString();if(n===Array){if((r=e.length)===t.length)for(;r--&&Zi(e[r],t[r]););return r===-1}if(!n||typeof e==`object`){for(n in r=0,e)if(Xi.call(e,n)&&++r&&!Xi.call(t,n)||!(n in t)||!Zi(e[n],t[n]))return!1;return Object.keys(t).length===r}}return e!==e&&t!==t}var Qi=new WeakMap,$i=()=>{},ea=Object,M=e=>e===void 0,N=e=>typeof e==`function`,ta=(e,t)=>({...e,...t}),na=e=>N(e.then),ra={},ia={},aa=`undefined`,oa=typeof window!=aa,sa=typeof document!=aa,ca=oa&&`Deno`in window,la=()=>oa&&typeof window.requestAnimationFrame!=aa,P=(e,t)=>{let n=Qi.get(e);return[()=>!M(t)&&e.get(t)||ra,r=>{if(!M(t)){let i=e.get(t);t in ia||(ia[t]=i),n[5](t,ta(i,r),i||ra)}},n[6],()=>!M(t)&&t in ia?ia[t]:!M(t)&&e.get(t)||ra]},F=!0,ua=()=>F,[da,fa]=oa&&window.addEventListener?[window.addEventListener.bind(window),window.removeEventListener.bind(window)]:[$i,$i],pa=()=>{let e=sa&&document.visibilityState;return M(e)||e!==`hidden`},ma=e=>(sa&&document.addEventListener(`visibilitychange`,e),da(`focus`,e),()=>{sa&&document.removeEventListener(`visibilitychange`,e),fa(`focus`,e)}),ha=e=>{let t=()=>{F=!0,e()},n=()=>{F=!1};return da(`online`,t),da(`offline`,n),()=>{fa(`online`,t),fa(`offline`,n)}},ga={isOnline:ua,isVisible:pa},_a={initFocus:ma,initReconnect:ha},va=!_.useId,ya=!oa||ca,ba=e=>la()?window.requestAnimationFrame(e):setTimeout(e,1),xa=ya?_.useEffect:_.useLayoutEffect,Sa=typeof navigator<`u`&&navigator.connection,Ca=!ya&&Sa&&([`slow-2g`,`2g`].includes(Sa.effectiveType)||Sa.saveData),wa=new WeakMap,Ta=e=>ea.prototype.toString.call(e),Ea=(e,t)=>e===`[object ${t}]`,Da=0,Oa=e=>{let t=typeof e,n=Ta(e),r=Ea(n,`Date`),i=Ea(n,`RegExp`),a=Ea(n,`Object`),o,s;if(ea(e)===e&&!r&&!i){if(o=wa.get(e),o)return o;if(o=++Da+`~`,wa.set(e,o),Array.isArray(e)){for(o=`@`,s=0;s<e.length;s++)o+=Oa(e[s])+`,`;wa.set(e,o)}if(a){o=`#`;let t=ea.keys(e).sort();for(;!M(s=t.pop());)M(e[s])||(o+=s+`:`+Oa(e[s])+`,`);wa.set(e,o)}}else o=r?e.toJSON():t==`symbol`?e.toString():t==`string`?JSON.stringify(e):``+e;return o},ka=e=>{if(N(e))try{e=e()}catch{e=``}let t=e;return e=typeof e==`string`?e:(Array.isArray(e)?e.length:e)?Oa(e):``,[e,t]},Aa=0,ja=()=>++Aa;async function Ma(...e){let[t,n,r,i]=e,a=ta({populateCache:!0,throwOnError:!0},typeof i==`boolean`?{revalidate:i}:i||{}),o=a.populateCache,s=a.rollbackOnError,c=a.optimisticData,l=e=>typeof s==`function`?s(e):s!==!1,u=a.throwOnError;if(N(n)){let e=n,r=[],i=t.keys();for(let n of i)!/^\$(inf|sub)\$/.test(n)&&e(t.get(n)._k)&&r.push(n);return Promise.all(r.map(d))}return d(n);async function d(n){let[i]=ka(n);if(!i)return;let[s,d]=P(t,i),[f,p,m,h]=Qi.get(t),g=()=>{let e=f[i];return(N(a.revalidate)?a.revalidate(s().data,n):a.revalidate!==!1)&&(delete m[i],delete h[i],e&&e[0])?e[0](2).then(()=>s().data):s().data};if(e.length<3)return g();let _=r,v,y=!1,b=ja();p[i]=[b,0];let ee=!M(c),te=s(),ne=te.data,re=te._c,x=M(re)?ne:re;if(ee&&(c=N(c)?c(x,ne):c,d({data:c,_c:x})),N(_))try{_=_(x)}catch(e){v=e,y=!0}if(_&&na(_)){if(_=await _.catch(e=>{v=e,y=!0}),b!==p[i][0]){if(y)throw v;return _}y&&ee&&l(v)&&(o=!0,d({data:x,_c:void 0}))}if(o&&(y||(N(o)?d({data:o(_,x),error:void 0,_c:void 0}):d({data:_,error:void 0,_c:void 0}))),p[i][1]=ja(),Promise.resolve(g()).then(()=>{d({_c:void 0})}),y){if(u)throw v;return}return _}}var Na=(e,t)=>{for(let n in e)e[n][0]&&e[n][0](t)},Pa= Line truncated
`,e.message]}):this.props.children}};(0,v.createRoot)(document.getElementById(`root`)).render((0,D.jsx)(_.StrictMode,{children:(0,D.jsx)(R,{children:(0,D.jsx)(Ye,{children:(0,D.jsx)(L,{})})})}));
+2 -2
View File
@@ -27,8 +27,8 @@
}
})()
</script>
<script type="module" crossorigin src="/assets/index-cXoFpIQ4.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DGNRMRfX.css">
<script type="module" crossorigin src="/assets/index-C60Osx-j.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B4mWbae6.css">
</head>
<body>
<div id="root"></div>
+1 -1
View File
@@ -1 +1 @@
{"root":["../../src/App.tsx","../../src/main.tsx","../../src/api/client.ts","../../src/api/items.ts","../../src/app/AppStateProvider.tsx","../../src/app/appState.ts","../../src/app/index.ts","../../src/app/persist.ts","../../src/app/routes.tsx","../../src/auth/SessionCard.tsx","../../src/auth/UserMenu.tsx","../../src/auth/useSession.ts","../../src/components/AIWorkflowSection.tsx","../../src/components/Button.tsx","../../src/components/ButtonMatrix.tsx","../../src/components/CodeEditor.tsx","../../src/components/CodeSample.tsx","../../src/components/FormField.tsx","../../src/components/Hero.tsx","../../src/components/Icon.tsx","../../src/components/IconMatrix.tsx","../../src/components/MarkdownRenderer.tsx","../../src/components/RightRail.tsx","../../src/components/SearchPalette.tsx","../../src/components/Section.tsx","../../src/components/Sidebar.tsx","../../src/components/TokenGrid.tsx","../../src/components/Topbar.tsx","../../src/components/TypographySection.tsx","../../src/components/icon-registry.ts","../../src/components/examples/DeploymentModal.tsx","../../src/components/examples/MetricsDashboard.tsx","../../src/components/examples/ServiceTable.tsx","../../src/components/examples/SettingsPanel.tsx","../../src/components/examples/index.ts","../../src/components/ui/Alert.tsx","../../src/components/ui/Avatar.tsx","../../src/components/ui/Badge.tsx","../../src/components/ui/Card.tsx","../../src/components/ui/Dialog.tsx","../../src/components/ui/EmptyState.tsx","../../src/components/ui/Kbd.tsx","../../src/components/ui/Overlay.tsx","../../src/components/ui/Progress.tsx","../../src/components/ui/Sheet.tsx","../../src/components/ui/Skeleton.tsx","../../src/components/ui/Spinner.tsx","../../src/components/ui/StatCard.tsx","../../src/components/ui/Switch.tsx","../../src/components/ui/Table.tsx","../../src/components/ui/Tabs.tsx","../../src/components/ui/Tooltip.tsx","../../src/components/ui/index.ts","../../src/components/ui/overlayBehavior.ts","../../src/data/library.ts","../../src/data/patterns.ts","../../src/dev/ErrorBoundary.tsx","../../src/dev/errorSink.ts","../../src/hooks/useItems.ts","../../src/pages/ButtonsPage.tsx","../../src/pages/ColorsPage.tsx","../../src/pages/EditorPage.tsx","../../src/pages/FormsPage.tsx","../../src/pages/IconsPage.tsx","../../src/pages/ItemsPage.tsx","../../src/pages/MarkdownPage.tsx","../../src/pages/NotFoundPage.tsx","../../src/pages/OverviewPage.tsx","../../src/pages/PatternsPage.tsx","../../src/pages/PrimitivesPage.tsx","../../src/pages/TypographyPage.tsx","../../src/utils/cn.ts","../../src/utils/useTimeout.ts"],"version":"6.0.3"}
{"root":["../../src/App.tsx","../../src/main.tsx","../../src/api/browse.ts","../../src/api/client.ts","../../src/api/items.ts","../../src/app/AppStateProvider.tsx","../../src/app/appState.ts","../../src/app/index.ts","../../src/app/persist.ts","../../src/app/routes.tsx","../../src/auth/SessionCard.tsx","../../src/auth/UserMenu.tsx","../../src/auth/useSession.ts","../../src/components/AIWorkflowSection.tsx","../../src/components/Button.tsx","../../src/components/ButtonMatrix.tsx","../../src/components/CodeEditor.tsx","../../src/components/CodeSample.tsx","../../src/components/FormField.tsx","../../src/components/Hero.tsx","../../src/components/Icon.tsx","../../src/components/IconMatrix.tsx","../../src/components/MarkdownRenderer.tsx","../../src/components/RightRail.tsx","../../src/components/SearchPalette.tsx","../../src/components/Section.tsx","../../src/components/Sidebar.tsx","../../src/components/TokenGrid.tsx","../../src/components/Topbar.tsx","../../src/components/TypographySection.tsx","../../src/components/icon-registry.ts","../../src/components/examples/DeploymentModal.tsx","../../src/components/examples/MetricsDashboard.tsx","../../src/components/examples/ServiceTable.tsx","../../src/components/examples/SettingsPanel.tsx","../../src/components/examples/index.ts","../../src/components/markdown/MarkdownView.tsx","../../src/components/markdown/parse.ts","../../src/components/ui/Alert.tsx","../../src/components/ui/Avatar.tsx","../../src/components/ui/Badge.tsx","../../src/components/ui/Card.tsx","../../src/components/ui/Dialog.tsx","../../src/components/ui/EmptyState.tsx","../../src/components/ui/Kbd.tsx","../../src/components/ui/Overlay.tsx","../../src/components/ui/Progress.tsx","../../src/components/ui/Sheet.tsx","../../src/components/ui/Skeleton.tsx","../../src/components/ui/Spinner.tsx","../../src/components/ui/StatCard.tsx","../../src/components/ui/Switch.tsx","../../src/components/ui/Table.tsx","../../src/components/ui/Tabs.tsx","../../src/components/ui/Tooltip.tsx","../../src/components/ui/index.ts","../../src/components/ui/overlayBehavior.ts","../../src/data/library.ts","../../src/data/patterns.ts","../../src/dev/ErrorBoundary.tsx","../../src/dev/errorSink.ts","../../src/hooks/useBrowse.ts","../../src/hooks/useItems.ts","../../src/pages/AgentViewPage.tsx","../../src/pages/ButtonsPage.tsx","../../src/pages/ColorsPage.tsx","../../src/pages/EditorPage.tsx","../../src/pages/FormsPage.tsx","../../src/pages/IconsPage.tsx","../../src/pages/ItemsPage.tsx","../../src/pages/MarkdownPage.tsx","../../src/pages/NotFoundPage.tsx","../../src/pages/OverviewPage.tsx","../../src/pages/PatternsPage.tsx","../../src/pages/PrimitivesPage.tsx","../../src/pages/TypographyPage.tsx","../../src/utils/cn.ts","../../src/utils/useTimeout.ts"],"version":"6.0.3"}
+11 -11
View File
@@ -1,55 +1,55 @@
{
"hash": "976b9c3d",
"configHash": "89e67727",
"hash": "b89571df",
"configHash": "98cfc040",
"lockfileHash": "0d4791a3",
"browserHash": "4b2fbac7",
"browserHash": "01f60908",
"optimized": {
"lucide-react": {
"src": "../../lucide-react/dist/esm/lucide-react.mjs",
"file": "lucide-react.js",
"fileHash": "6c3cfb80",
"fileHash": "503441b8",
"needsInterop": false
},
"react-dom": {
"src": "../../react-dom/index.js",
"file": "react-dom.js",
"fileHash": "25030b7a",
"fileHash": "f0a88d8a",
"needsInterop": true
},
"react-dom/client": {
"src": "../../react-dom/client.js",
"file": "react-dom_client.js",
"fileHash": "de15c997",
"fileHash": "20f9fc06",
"needsInterop": true
},
"react": {
"src": "../../react/index.js",
"file": "react.js",
"fileHash": "e97c5b5e",
"fileHash": "1f16888b",
"needsInterop": true
},
"react/jsx-dev-runtime": {
"src": "../../react/jsx-dev-runtime.js",
"file": "react_jsx-dev-runtime.js",
"fileHash": "15b8f65b",
"fileHash": "f6e1330a",
"needsInterop": true
},
"react/jsx-runtime": {
"src": "../../react/jsx-runtime.js",
"file": "react_jsx-runtime.js",
"fileHash": "6d9e10ca",
"fileHash": "ea33db31",
"needsInterop": true
},
"swr": {
"src": "../../swr/dist/index/index.mjs",
"file": "swr.js",
"fileHash": "5dedc4fa",
"fileHash": "43f93c02",
"needsInterop": false
},
"wouter": {
"src": "../../wouter/src/index.js",
"file": "wouter.js",
"fileHash": "9e6a9f9c",
"fileHash": "ebb90d1b",
"needsInterop": false
}
},
@@ -1 +1 @@
{"version":"5.0.0","results":[[":tests/auth.test.tsx",{"duration":491.67805499999986,"failed":false}],[":tests/api.test.ts",{"duration":14.961915999999974,"failed":false}],[":tests/overlay.test.tsx",{"duration":728.093953,"failed":false}],[":tests/check-tokens.test.mjs",{"duration":998.8133460000001,"failed":false}],[":tests/routing.test.tsx",{"duration":1758.9155749999995,"failed":false}],[":tests/appState.test.tsx",{"duration":423.0312829999998,"failed":false}],[":tests/icon-registry.test.tsx",{"duration":75.86277799999993,"failed":false}],[":tests/useTimeout.test.tsx",{"duration":43.350607999999966,"failed":false}],[":tests/useItems.test.tsx",{"duration":415.8608149999998,"failed":false}],[":tests/itemsPage.test.tsx",{"duration":698.4244530000001,"failed":false}]]}
{"version":"5.0.0","results":[[":tests/auth.test.tsx",{"duration":536.224841,"failed":false}],[":tests/api.test.ts",{"duration":17.88092000000006,"failed":false}],[":tests/overlay.test.tsx",{"duration":694.3132250000001,"failed":false}],[":tests/check-tokens.test.mjs",{"duration":734.5557109999997,"failed":false}],[":tests/routing.test.tsx",{"duration":2000.0288979999996,"failed":false}],[":tests/appState.test.tsx",{"duration":367.28223000000025,"failed":false}],[":tests/icon-registry.test.tsx",{"duration":80.47717699999998,"failed":false}],[":tests/useTimeout.test.tsx",{"duration":56.3337939999999,"failed":false}],[":tests/useItems.test.tsx",{"duration":398.72940700000004,"failed":false}],[":tests/itemsPage.test.tsx",{"duration":708.2502969999998,"failed":false}],[":tests/agentViewPage.test.tsx",{"duration":1352.459346,"failed":false}],[":tests/markdown.test.ts",{"duration":18.204109000000017,"failed":false}],[":tests/dbg.test.tsx",{"duration":625.5198799999998,"failed":false}]]}
+297
View File
@@ -2226,3 +2226,300 @@ footer {
@media (max-width: 760px) {
.overview-grid { padding-bottom: var(--space-9); }
}
/* ── Agent view ─────────────────────────────────────────────────────────
The LLM browser: domain in, agent-eye view out. All colors come from
tokens; derived shades use color-mix so both themes stay correct. */
.agent-url-row {
display: flex;
gap: var(--space-2);
flex-wrap: wrap;
}
.agent-url-row input {
flex: 1;
min-width: min(100%, 22rem);
}
.agent-examples {
display: flex;
align-items: center;
gap: var(--space-2);
flex-wrap: wrap;
margin-top: var(--space-3);
}
.agent-example {
padding: var(--space-1) var(--space-3);
font-size: var(--text-xs);
font-family: var(--font-mono);
}
.agent-head .agent-url {
font-size: var(--text-sm);
color: var(--foreground);
word-break: break-all;
}
.agent-note {
font-size: var(--text-xs);
padding: 0 var(--space-2);
}
.agent-doc {
padding: var(--space-5);
overflow-x: auto;
}
/* The converted page renders inside the shared .markdown-body editorial
styles; relax its centered-article metrics for the app surface and fill
in the shapes the demo never needed (links, lists, tables, code). */
.agent-doc .markdown-body {
max-width: none;
margin: 0;
padding: 0;
}
.agent-doc .markdown-body h1 {
font-size: var(--text-2xl);
letter-spacing: -0.04em;
line-height: 1.15;
margin: var(--space-5) 0 var(--space-3);
}
.agent-doc .markdown-body h2 {
font-size: var(--text-xl);
letter-spacing: -0.03em;
line-height: 1.2;
margin: var(--space-5) 0 var(--space-2);
}
.agent-doc .markdown-body h5,
.agent-doc .markdown-body h6 {
font-size: var(--text-md);
margin: var(--space-4) 0 var(--space-1);
}
.agent-doc .markdown-body a {
color: var(--accent);
text-decoration: underline;
text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent);
text-underline-offset: 2px;
}
.agent-doc .markdown-body a:hover {
text-decoration-color: var(--accent);
}
.agent-doc .markdown-body ul,
.agent-doc .markdown-body ol {
margin: 0 0 var(--space-4);
padding-left: 1.4rem;
color: var(--muted-foreground);
display: flex;
flex-direction: column;
gap: var(--space-1);
}
.agent-doc .markdown-body blockquote {
margin: 0 0 var(--space-4);
padding: var(--space-2) var(--space-4);
border-left: 2px solid color-mix(in srgb, var(--accent) 55%, var(--border));
color: var(--muted-foreground);
font-style: italic;
}
.agent-doc .markdown-body blockquote p {
margin: 0;
}
.agent-doc .markdown-body pre {
margin: 0 0 var(--space-4);
padding: var(--space-3) var(--space-4);
background: var(--muted);
border: 1px solid var(--border);
border-radius: var(--radius-md);
overflow-x: auto;
}
.agent-doc .markdown-body pre code {
background: none;
border: none;
padding: 0;
color: var(--foreground);
font-size: var(--text-xs);
line-height: 1.6;
}
.agent-doc .markdown-body table {
width: 100%;
border-collapse: collapse;
margin: 0 0 var(--space-4);
font-size: var(--text-sm);
}
.agent-doc .markdown-body th,
.agent-doc .markdown-body td {
border: 1px solid var(--border);
padding: var(--space-2) var(--space-3);
text-align: left;
}
.agent-doc .markdown-body th {
background: color-mix(in srgb, var(--accent) 7%, var(--card));
}
.agent-doc .markdown-body hr {
border: none;
border-top: 1px solid var(--border);
margin: var(--space-5) 0;
}
.agent-doc .markdown-body img {
max-width: 100%;
border-radius: var(--radius-sm);
}
/* Raw source + companion files */
.agent-raw {
margin: 0;
padding: var(--space-4);
background: var(--muted);
color: var(--foreground);
font-size: var(--text-xs);
line-height: 1.6;
overflow: auto;
max-height: 26rem;
white-space: pre-wrap;
word-break: break-word;
}
.agent-file-url {
font-size: var(--text-2xs);
text-decoration: none;
word-break: break-all;
}
.agent-file-url:hover {
color: var(--foreground);
}
/* Parsed llms.txt */
.agent-llms {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.agent-llms-title {
margin: 0;
font-size: var(--text-lg);
letter-spacing: -0.02em;
}
.agent-llms-quote {
margin: 0;
padding: var(--space-2) var(--space-4);
border-left: 2px solid color-mix(in srgb, var(--accent) 55%, var(--border));
color: var(--muted-foreground);
font-style: italic;
font-size: var(--text-sm);
}
.agent-llms-section h4 {
margin: var(--space-2) 0 0;
font-size: var(--text-md);
}
.agent-llms-section > p {
margin: 0;
font-size: var(--text-xs);
}
.agent-llms-section ul {
list-style: disc;
margin: var(--space-2) 0 0;
padding-left: 1.3rem;
display: flex;
flex-direction: column;
gap: var(--space-1);
font-size: var(--text-sm);
}
.agent-llms-section a {
color: var(--accent);
text-decoration: underline;
text-underline-offset: 2px;
}
/* Meta table */
.agent-meta {
margin: 0;
padding: var(--space-2) var(--space-5) var(--space-4);
}
.agent-meta-row {
display: grid;
grid-template-columns: minmax(110px, max-content) 1fr;
gap: var(--space-4);
padding: var(--space-2) 0;
border-bottom: 1px solid var(--border);
font-size: var(--text-sm);
}
.agent-meta-row:last-child {
border-bottom: none;
}
.agent-meta-row dd {
margin: 0;
word-break: break-word;
}
/* Checks */
.agent-checks-summary {
padding: var(--space-4) var(--space-5) 0;
}
.agent-checks {
list-style: none;
margin: 0;
padding: var(--space-2) var(--space-5) var(--space-4);
}
.agent-check {
display: flex;
align-items: flex-start;
gap: var(--space-3);
padding: var(--space-3) 0;
border-bottom: 1px solid var(--border);
}
.agent-check:last-child {
border-bottom: none;
}
.agent-check .badge {
flex-shrink: 0;
min-width: 3.5rem;
justify-content: center;
text-transform: uppercase;
}
.agent-check-label {
font-weight: 600;
font-size: var(--text-sm);
}
.agent-check-detail {
font-size: var(--text-xs);
line-height: 1.55;
}
@media (max-width: 720px) {
.agent-meta-row {
grid-template-columns: 1fr;
gap: var(--space-1);
}
}
+82
View File
@@ -0,0 +1,82 @@
/* Typed client for the browse resource — the agent view. The wire format
is owned by backend/internal/browse (model.go); these types mirror it. */
import { apiFetch } from './client'
export type Severity = 'ok' | 'info' | 'warn' | 'error'
export type Check = {
id: string
label: string
severity: Severity
detail: string
}
export type PageView = {
title: string
description: string
canonical: string
robotsMeta: string
lang: string
h1: string
linkCount: number
deadLinks: number
scriptCount: number
imageCount: number
wordCount: number
textLength: number
}
export type LlmsLink = {
title: string
url: string
description: string
}
export type LlmsSection = {
heading: string
description: string
links: LlmsLink[]
}
export type LlmsDoc = {
title: string
description: string
sections: LlmsSection[]
linkCount: number
}
export type FileResult = {
url: string
status: number
contentType: string
error?: string
content: string
truncated: boolean
llmsDoc?: LlmsDoc
}
export type BrowseFiles = {
llmsTxt: FileResult | null
llmsFullTxt: FileResult | null
wellKnownLlmsTxt: FileResult | null
robotsTxt: FileResult | null
}
export type Snapshot = {
requestedUrl: string
finalUrl: string
status: number
contentType: string
durationMs: number
fetchedAt: string
page?: PageView
markdown: string
markdownTruncated: boolean
files: BrowseFiles
checks: Check[]
}
/** GET /api/v1/browse?url=… — a bare domain or a full URL both work. */
export function browse(url: string): Promise<Snapshot> {
return apiFetch<Snapshot>(`/api/v1/browse?url=${encodeURIComponent(url)}`)
}
+26 -10
View File
@@ -11,6 +11,7 @@ import { PrimitivesPage } from '../pages/PrimitivesPage'
import { MarkdownPage } from '../pages/MarkdownPage'
import { EditorPage } from '../pages/EditorPage'
import { ItemsPage } from '../pages/ItemsPage'
import { AgentViewPage } from '../pages/AgentViewPage'
/* ── The route table ─────────────────────────────────────────────────────────
One registry drives the router, the sidebar, the breadcrumbs and the ⌘K
@@ -23,6 +24,9 @@ export const PATTERNS_PATH = '/patterns'
/** Live backend demo page (vite proxies /api to the Go server on :8080). */
export const ITEMS_PATH = '/items'
/** The agent view: enter a domain, see what an LLM sees. */
export const AGENT_VIEW_PATH = '/agent-view'
export const ROUTE_GROUPS = ['Application Patterns', 'Components', 'Foundations', 'Documentation'] as const
export type RouteGroup = (typeof ROUTE_GROUPS)[number]
@@ -50,11 +54,23 @@ export type AppRoute = {
}
export const routes: AppRoute[] = [
{
path: AGENT_VIEW_PATH,
group: 'Application Patterns',
label: 'Agent view',
index: '01',
kicker: 'AGENT TOOLING',
title: 'Agent view — browse as an LLM',
description:
'Enter a domain and see what an agent sees: the page as markdown with followable links, its llms.txt map, robots rules and readiness checks.',
meta: <span className="api-pill">/api/v1/browse · llms.txt</span>,
page: AgentViewPage,
},
{
path: PATTERNS_PATH,
group: 'Application Patterns',
label: 'Application Patterns',
index: '01',
index: '02',
kicker: 'PATTERNS & EXAMPLES',
title: 'Reusable Application Patterns',
description:
@@ -66,7 +82,7 @@ export const routes: AppRoute[] = [
path: '/components/buttons',
group: 'Components',
label: 'Buttons',
index: '02',
index: '03',
kicker: 'COMPONENTS',
title: 'Buttons',
description: 'Actions with a clear hierarchy and dependable feedback.',
@@ -77,7 +93,7 @@ export const routes: AppRoute[] = [
path: '/components/icons',
group: 'Components',
label: 'Icons',
index: '03',
index: '04',
kicker: 'COMPONENTS',
title: 'Icons (Lucide)',
description:
@@ -89,7 +105,7 @@ export const routes: AppRoute[] = [
path: '/foundations/colors',
group: 'Foundations',
label: 'Color tokens',
index: '04',
index: '05',
kicker: 'FOUNDATIONS',
title: 'Color tokens',
description: 'Functional color roles in CSS Color 4 lab() keep every surface intentional.',
@@ -100,7 +116,7 @@ export const routes: AppRoute[] = [
path: '/foundations/typography',
group: 'Foundations',
label: 'Typography',
index: '05',
index: '06',
kicker: 'FOUNDATIONS',
title: 'Typography',
description: 'Modular rem-based type scale with dual-family hierarchy (Manrope & DM Mono).',
@@ -111,7 +127,7 @@ export const routes: AppRoute[] = [
path: '/components/forms',
group: 'Components',
label: 'Form controls',
index: '06',
index: '07',
kicker: 'PRIMITIVES',
title: 'Form controls',
description: 'Inputs designed for focus, flow, and the occasional messy edge case.',
@@ -121,7 +137,7 @@ export const routes: AppRoute[] = [
path: '/components/code',
group: 'Components',
label: 'Composable code',
index: '07',
index: '08',
kicker: 'CODE',
title: 'Composable by default',
description: 'Small APIs. Strong defaults. No surprises.',
@@ -131,7 +147,7 @@ export const routes: AppRoute[] = [
path: '/docs/markdown',
group: 'Documentation',
label: 'Markdown rendering',
index: '08',
index: '09',
kicker: 'CONTENT',
title: 'Markdown rendering',
description: 'A readable editorial layer for changelogs, docs, and product notes.',
@@ -142,7 +158,7 @@ export const routes: AppRoute[] = [
path: '/docs/editor',
group: 'Documentation',
label: 'CodeMirror theme',
index: '09',
index: '10',
kicker: 'DEVELOPER TOOLING',
title: 'CodeMirror theme',
description: 'A focused editor palette for examples, playgrounds, and embedded code.',
@@ -153,7 +169,7 @@ export const routes: AppRoute[] = [
path: ITEMS_PATH,
group: 'Application Patterns',
label: 'Items (live API)',
index: '10',
index: '11',
kicker: 'FULLSTACK',
title: 'Live data — Go API + SWR',
description:
@@ -18,6 +18,14 @@ export type SearchItem = {
const SEARCH_INDEX: SearchItem[] = [
// Patterns
{
id: 'pat-agent-view',
title: 'Agent view — browse as an LLM',
description: 'Enter a domain, see the page as markdown, its llms.txt map and readiness checks',
category: 'Patterns',
icon: 'cpu',
path: '/agent-view',
},
{
id: 'pat-metrics',
title: 'Metrics Dashboard',
@@ -0,0 +1,139 @@
/* React renderer for the agent view's markdown. Builds elements directly
from parse.ts output — no HTML injection. Every link click is routed
through onNavigate so "browsing" stays inside the agent view: the viewer
fetches the linked page and renders it the same way, which is exactly
the loop an agent is in. */
import { useMemo, type AnchorHTMLAttributes, type ReactNode } from 'react'
import { parseMarkdown, type InlineSpan, type ListBlock, type MarkdownBlock } from './parse'
export function MarkdownView({
source,
onNavigate,
className,
}: {
source: string
/** Called with the absolute href when the visitor opens a link. */
onNavigate?: (href: string) => void
className?: string
}) {
const blocks = useMemo(() => parseMarkdown(source), [source])
return (
<div className={className}>
{blocks.map((block, index) => (
<BlockView key={index} block={block} onNavigate={onNavigate} />
))}
</div>
)
}
function BlockView({ block, onNavigate }: { block: MarkdownBlock; onNavigate?: (href: string) => void }) {
switch (block.kind) {
case 'heading': {
const Tag = `h${block.level}` as 'h1'
return <Tag>{block.spans.map((span, i) => <SpanView key={i} span={span} onNavigate={onNavigate} />)}</Tag>
}
case 'paragraph':
return <p>{block.spans.map((span, i) => <SpanView key={i} span={span} onNavigate={onNavigate} />)}</p>
case 'code':
return (
<pre>
<code>{block.text}</code>
</pre>
)
case 'quote':
return (
<blockquote>
{block.blocks.map((child, i) => (
<BlockView key={i} block={child} onNavigate={onNavigate} />
))}
</blockquote>
)
case 'list':
return <ListView list={block} onNavigate={onNavigate} />
case 'table':
return (
<table>
<thead>
<tr>
{block.head.map((cell, i) => (
<th key={i}>{cell}</th>
))}
</tr>
</thead>
<tbody>
{block.rows.map((row, r) => (
<tr key={r}>
{row.map((cell, c) => (
<td key={c}>{cell}</td>
))}
</tr>
))}
</tbody>
</table>
)
case 'hr':
return <hr />
}
}
function ListView({ list, onNavigate }: { list: ListBlock; onNavigate?: (href: string) => void }) {
const Tag = list.ordered ? 'ol' : 'ul'
return (
<Tag start={list.ordered && list.start !== 1 ? list.start : undefined}>
{list.items.map((item, index) => (
<li key={index}>
{item.spans.map((span, i) => (
<SpanView key={i} span={span} onNavigate={onNavigate} />
))}
{item.sub && <ListView list={item.sub} onNavigate={onNavigate} />}
</li>
))}
</Tag>
)
}
function SpanView({ span, onNavigate }: { span: InlineSpan; onNavigate?: (href: string) => void }) {
switch (span.kind) {
case 'text':
return <>{span.text}</>
case 'code':
return <code>{span.text}</code>
case 'strong':
return <strong>{span.spans.map((child, i) => <SpanView key={i} span={child} onNavigate={onNavigate} />)}</strong>
case 'em':
return <em>{span.spans.map((child, i) => <SpanView key={i} span={child} onNavigate={onNavigate} />)}</em>
case 'del':
return <del>{span.spans.map((child, i) => <SpanView key={i} span={child} onNavigate={onNavigate} />)}</del>
case 'image':
return <img src={span.src} alt={span.alt} loading="lazy" />
case 'link':
return <AgentLink href={span.href} onNavigate={onNavigate}>{renderInline(span.spans, onNavigate)}</AgentLink>
}
}
function AgentLink({
href,
onNavigate,
children,
...props
}: AnchorHTMLAttributes<HTMLAnchorElement> & { href: string; onNavigate?: (href: string) => void }) {
const followable = /^https?:\/\//i.test(href)
return (
<a
href={href}
onClick={event => {
if (!followable || !onNavigate) return
event.preventDefault() // the agent view never leaves itself
onNavigate(href)
}}
title={followable ? 'Open in the agent view' : undefined}
{...props}
>
{children}
</a>
)
}
function renderInline(spans: InlineSpan[], onNavigate?: (href: string) => void): ReactNode {
return spans.map((span, i) => <SpanView key={i} span={span} onNavigate={onNavigate} />)
}
+255
View File
@@ -0,0 +1,255 @@
/* A small markdown block/inline parser for the agent view. The backend
converts HTML to well-behaved markdown (headings, paragraphs, lists,
fenced code, blockquotes, GFM tables, links, images) — exactly the shapes
this parser handles, so the renderer can build React elements directly
without dangerouslySetInnerHTML. Unknown syntax degrades to paragraphs. */
export type InlineSpan =
| { kind: 'text'; text: string }
| { kind: 'code'; text: string }
| { kind: 'strong'; spans: InlineSpan[] }
| { kind: 'em'; spans: InlineSpan[] }
| { kind: 'del'; spans: InlineSpan[] }
| { kind: 'link'; href: string; spans: InlineSpan[] }
| { kind: 'image'; alt: string; src: string }
export type ListItem = {
spans: InlineSpan[]
/** A list nested under this item. */
sub?: ListBlock
}
export type ListBlock = {
kind: 'list'
ordered: boolean
start: number
items: ListItem[]
}
export type MarkdownBlock =
| { kind: 'heading'; level: number; spans: InlineSpan[] }
| { kind: 'paragraph'; spans: InlineSpan[] }
| { kind: 'code'; language: string; text: string }
| { kind: 'quote'; blocks: MarkdownBlock[] }
| ListBlock
| { kind: 'table'; head: string[]; rows: string[][] }
| { kind: 'hr' }
const headingRe = /^(#{1,6})\s+(.*)$/
const hrRe = /^ {0,3}(?:-{3,}|\*{3,}|_{3,})\s*$/
const fenceRe = /^ {0,3}(`{3,}|~{3,})\s*([^\s`]*)\s*$/
const quoteRe = /^ {0,3}>\s?(.*)$/
const itemRe = /^(\s*)([-*+]|\d{1,9}[.)])\s+(.*)$/
const tableRowRe = /^\s*.*\|.*$/
const tableSepRe = /^\s*\|?\s*:?-{2,}[\s:|-]*\|?\s*$/
/** Parse a markdown document into renderable blocks. */
export function parseMarkdown(source: string): MarkdownBlock[] {
return parseBlocks(source.split('\n')).blocks
}
function parseBlocks(lines: string[]): { blocks: MarkdownBlock[]; next: number } {
const blocks: MarkdownBlock[] = []
let i = 0
outer: while (i < lines.length) {
const line = lines[i]
if (line === undefined) break
if (line.trim() === '') {
i++
continue
}
const fence = fenceRe.exec(line)
if (fence) {
const marker = fence[1] ?? '`'
const language = fence[2] ?? ''
const body: string[] = []
i++
while (i < lines.length) {
const current = lines[i]
if (current === undefined) break
if (new RegExp(`^ {0,3}${marker[0]}{${marker.length},}\\s*$`).test(current)) break
body.push(current)
i++
}
i++ // closing fence (or EOF)
blocks.push({ kind: 'code', language, text: body.join('\n') })
continue
}
const heading = headingRe.exec(line)
if (heading) {
blocks.push({
kind: 'heading',
level: heading[1]?.length ?? 1,
spans: parseInline(heading[2] ?? ''),
})
i++
continue
}
if (hrRe.test(line)) {
blocks.push({ kind: 'hr' })
i++
continue
}
const quote = quoteRe.exec(line)
if (quote) {
const inner: string[] = [quote[1] ?? '']
i++
while (i < lines.length) {
const cont = quoteRe.exec(lines[i] ?? '')
if (!cont) break
inner.push(cont[1] ?? '')
i++
}
blocks.push({ kind: 'quote', blocks: parseBlocks(inner).blocks })
continue
}
const item = itemRe.exec(line)
if (item) {
const parsed = parseList(lines, i, item[1]?.length ?? 0)
blocks.push(parsed.list)
i = parsed.next
continue
}
const nextLine = lines[i + 1]
if (tableRowRe.test(line) && nextLine !== undefined && tableSepRe.test(nextLine)) {
const head = splitTableRow(line)
i += 2
const rows: string[][] = []
while (i < lines.length) {
const rowLine = lines[i]
if (rowLine === undefined || !tableRowRe.test(rowLine) || rowLine.trim() === '') break
rows.push(splitTableRow(rowLine))
i++
}
blocks.push({ kind: 'table', head, rows })
continue
}
// paragraph: gather until a blank line or the start of another block
const text: string[] = [line]
i++
while (i < lines.length) {
const next = lines[i]
if (next === undefined) break
if (
next.trim() === '' ||
headingRe.test(next) ||
hrRe.test(next) ||
fenceRe.test(next) ||
quoteRe.test(next) ||
itemRe.test(next)
) {
break
}
text.push(next)
i++
}
blocks.push({ kind: 'paragraph', spans: parseInline(text.join(' ').trim()) })
if (blocks.length > 5000) break outer // pathological input guard
}
return { blocks, next: i }
}
/** Parse one (possibly nested) list starting at `start` with base indent. */
function parseList(
lines: string[],
start: number,
indent: number,
): { list: ListBlock; next: number } {
const first = itemRe.exec(lines[start] ?? '')
const firstMarker = first?.[2] ?? ''
const ordered = /\d/.test(firstMarker)
const startNumber = ordered ? parseInt(firstMarker, 10) || 1 : 1
const items: ListItem[] = []
let i = start
while (i < lines.length) {
const line = lines[i]
if (line === undefined) break
if (line.trim() === '') {
// a blank line only ends the list if the next line is not a sibling item
if (i + 1 < lines.length && siblingItem(lines[i + 1] ?? '', indent)) {
i++
continue
}
break
}
const match = itemRe.exec(line)
if (!match) break
const itemIndent = match[1]?.length ?? 0
if (itemIndent < indent) break
if (itemIndent > indent) {
// deeper indent: nested list under the previous item
if (items.length > 0) {
const nested = parseList(lines, i, itemIndent)
const parent = items[items.length - 1]
if (parent) parent.sub = nested.list
i = nested.next
continue
}
break
}
const marker = match[2] ?? ''
if (ordered !== /\d/.test(marker)) break
items.push({ spans: parseInline(match[3] ?? '') })
i++
}
return { list: { kind: 'list', ordered, start: startNumber, items }, next: i }
}
function siblingItem(line: string, indent: number): boolean {
const match = itemRe.exec(line)
return match !== null && (match[1]?.length ?? 0) === indent
}
function splitTableRow(line: string): string[] {
let s = line.trim()
if (s.startsWith('|')) s = s.slice(1)
if (s.endsWith('|')) s = s.slice(0, -1)
return s.split('|').map(cell => cell.trim())
}
/* ── Inline parsing ──────────────────────────────────────────────────── */
const spanRe =
/(!\[[^\]]*\]\([^)]*\))|(\[[^\]]*\]\([^)]*\))|(`[^`\n]+`)|(\*\*[^*\n]+\*\*)|(__[^_\n]+__)|(~~[^~\n]+~~)|(\*[^*\n]+\*)|(_[^_\n]+_)/g
export function parseInline(text: string): InlineSpan[] {
const spans: InlineSpan[] = []
let last = 0
for (const match of text.matchAll(spanRe)) {
const index = match.index ?? 0
if (index > last) spans.push({ kind: 'text', text: text.slice(last, index) })
spans.push(...parseSpan(match[0]))
last = index + match[0].length
}
if (last < text.length) spans.push({ kind: 'text', text: text.slice(last) })
return spans
}
function parseSpan(raw: string): InlineSpan[] {
if (raw.startsWith('![')) {
const m = /^\!\[([^\]]*)\]\(([^)\s]*)(?:\s+"[^"]*")?\)$/.exec(raw)
if (m) return [{ kind: 'image', alt: m[1] ?? '', src: m[2] ?? '' }]
}
if (raw.startsWith('[')) {
const m = /^\[([^\]]*)\]\(([^)\s]*)(?:\s+"[^"]*")?\)$/.exec(raw)
if (m) return [{ kind: 'link', href: m[2] ?? '', spans: parseInline(m[1] ?? '') }]
}
if (raw.startsWith('`')) return [{ kind: 'code', text: raw.slice(1, -1) }]
if (raw.startsWith('~~')) return [{ kind: 'del', spans: parseInline(raw.slice(2, -2)) }]
if (raw.startsWith('**') || raw.startsWith('__')) {
return [{ kind: 'strong', spans: parseInline(raw.slice(2, -2)) }]
}
return [{ kind: 'em', spans: parseInline(raw.slice(1, -1)) }]
}
+20
View File
@@ -0,0 +1,20 @@
/* SWR data hook for the agent view. A null url means "idle" — no request
goes out until the visitor submits a domain. Snapshots are keyed by URL
so navigating back to a previously inspected page is instant from cache;
refresh() re-runs the fetch (sites change). */
import useSWR from 'swr'
import * as browseApi from '../api/browse'
import type { Snapshot } from '../api/browse'
export const browseKeys = {
snapshot: (url: string) => ['browse', 'snapshot', url] as const,
}
export function useBrowse(url: string | null) {
const { data, error, isLoading, mutate } = useSWR<Snapshot>(
url !== null ? browseKeys.snapshot(url) : null,
() => browseApi.browse(url as string),
{ keepPreviousData: true },
)
return { snapshot: data, error, isLoading, refresh: mutate }
}
+503
View File
@@ -0,0 +1,503 @@
/* The agent view: enter a domain and see the page the way a text-only
agent sees it — markdown, llms.txt companions, robots.txt and the
readiness checks. The inspected URL lives in ?url= so any page is
linkable, and link clicks re-enter the viewer (the agent loop). */
import { useState, type FormEvent } from 'react'
import { useLocation, useSearch } from 'wouter'
import {
Alert,
AlertDescription,
AlertTitle,
Badge,
Button,
Card,
CardDescription,
CardHeader,
CardTitle,
EmptyState,
EmptyStateActions,
EmptyStateDescription,
EmptyStateIcon,
EmptyStateTitle,
Icon,
Skeleton,
Spinner,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from '../components/ui'
import { MarkdownView } from '../components/markdown/MarkdownView'
import { TextInput } from '../components/FormField'
import { ApiError } from '../api/client'
import type { Check, FileResult, LlmsDoc, Severity, Snapshot } from '../api/browse'
import { useBrowse } from '../hooks/useBrowse'
const EXAMPLES = ['anthropic.com', 'example.com', 'developer.mozilla.org']
/** What to send to the API: ?url= is already normalized server-side. */
function urlFromSearch(search: string): string | null {
const value = new URLSearchParams(search).get('url')?.trim()
return value ? value : null
}
export function AgentViewPage() {
const [location, navigate] = useLocation()
const search = useSearch()
const activeUrl = urlFromSearch(search)
const [input, setInput] = useState(activeUrl ?? '')
const [tab, setTab] = useState('markdown')
const { snapshot, error, isLoading, refresh } = useBrowse(activeUrl)
/** Enter the agent loop at another URL (form submit or link click). */
const goTo = (value: string) => {
const target = value.trim()
if (!target) return
setInput(target)
setTab('markdown')
navigate(`${location.split('?')[0]}?url=${encodeURIComponent(target)}`)
}
const handleSubmit = (event: FormEvent) => {
event.preventDefault()
goTo(input)
}
return (
<div className="stack-md">
<Card>
<CardHeader>
<CardTitle>Browse as an agent</CardTitle>
<CardDescription>
Fetches the page server-side without JavaScript, converts it to markdown, and gathers
the <code className="mono">llms.txt</code> files — exactly what an LLM sees when it
“opens” this site.
</CardDescription>
</CardHeader>
<form className="agent-url-row" onSubmit={handleSubmit}>
<TextInput
value={input}
onChange={event => setInput(event.target.value)}
placeholder="example.com or https://example.com/docs"
aria-label="Domain or URL to inspect"
autoFocus
/>
<Button type="submit" variant="primary" disabled={input.trim() === '' || (isLoading && activeUrl !== null)}>
{isLoading && activeUrl !== null ? <Spinner size="xs" /> : <Icon name="search" size={14} />}
View as agent
</Button>
</form>
<div className="agent-examples">
{EXAMPLES.map(example => (
<button key={example} type="button" className="button ghost agent-example" onClick={() => goTo(example)}>
{example}
</button>
))}
</div>
</Card>
{error && !snapshot ? (
<BrowseError error={error} onRetry={() => refresh()} />
) : !activeUrl ? (
<ExplainCard />
) : isLoading && !snapshot ? (
<LoadingCard />
) : snapshot ? (
<SnapshotView snapshot={snapshot} tab={tab} onTab={setTab} onNavigate={goTo} onRefresh={() => refresh()} />
) : null}
</div>
)
}
/* ── States ─────────────────────────────────────────────────────────── */
function ExplainCard() {
return (
<EmptyState>
<EmptyStateIcon>
<Icon name="cpu" size={26} />
</EmptyStateIcon>
<EmptyStateTitle>What does your site look like to an LLM?</EmptyStateTitle>
<EmptyStateDescription>
Agents never run JavaScript and often skip the HTML entirely: they read markdown and{' '}
<code className="mono">llms.txt</code>. Enter a domain to validate what they actually get —
the rendered markdown, the curated link map, robots rules, and a list of fixes.
</EmptyStateDescription>
<EmptyStateActions>
{EXAMPLES.map(example => (
<AgentViewLinkButton key={example} url={example} />
))}
</EmptyStateActions>
</EmptyState>
)
}
/** Example trigger kept separate so tests can reach it by role. */
function AgentViewLinkButton({ url }: { url: string }) {
const [location, navigate] = useLocation()
return (
<Button
variant="secondary"
onClick={() => navigate(`${location.split('?')[0]}?url=${encodeURIComponent(url)}`)}
>
<Icon name="external-link" size={13} /> {url}
</Button>
)
}
function LoadingCard() {
return (
<Card className="agent-doc">
<div className="stack-sm">
<Skeleton width="40%" height={28} />
<Skeleton width="100%" />
<Skeleton width="92%" />
<Skeleton width="96%" />
<Skeleton width="60%" />
</div>
</Card>
)
}
function BrowseError({ error, onRetry }: { error: Error; onRetry: () => void }) {
const code = error instanceof ApiError ? error.code : 'unknown'
const siteFailed = code === 'unavailable' || code === 'timeout'
return (
<Alert variant={siteFailed ? 'warning' : 'destructive'} icon={<Icon name={siteFailed ? 'warning' : 'alert'} size={18} />}>
<AlertTitle>{siteFailed ? 'The site did not answer' : 'Inspection failed'}</AlertTitle>
<AlertDescription>
{siteFailed
? 'The server could not fetch this URL within the time limit — it may be down, very slow, or blocking the fetch.'
: error.message}
</AlertDescription>
<div className="row-xs" style={{ marginTop: 'var(--space-3)' }}>
<Button variant="secondary" onClick={onRetry}>
<Icon name="refresh" size={13} /> Try again
</Button>
</div>
</Alert>
)
}
/* ── The snapshot ───────────────────────────────────────────────────── */
function SnapshotView({
snapshot,
tab,
onTab,
onNavigate,
onRefresh,
}: {
snapshot: Snapshot
tab: string
onTab: (value: string) => void
onNavigate: (href: string) => void
onRefresh: () => void
}) {
const files = snapshot.files
const llmsFound = [files.llmsTxt, files.llmsFullTxt, files.wellKnownLlmsTxt].some(file => file?.status && file.status < 400)
const counts = countSeverities(snapshot.checks)
return (
<div className="stack-md">
<div className="flex-between agent-head" style={{ flexWrap: 'wrap', gap: 'var(--space-3)' }}>
<div className="row-sm" style={{ alignItems: 'center', flexWrap: 'wrap' }}>
<StatusBadge status={snapshot.status} />
<span className="mono agent-url">{snapshot.finalUrl}</span>
{snapshot.requestedUrl !== snapshot.finalUrl && (
<Badge variant="outline">redirected from {snapshot.requestedUrl}</Badge>
)}
<span className="muted" style={{ fontSize: 'var(--text-xs)' }}>
{snapshot.durationMs} ms · {snapshot.contentType.split(';')[0] || 'unknown type'}
</span>
</div>
<Button variant="ghost" onClick={onRefresh}>
<Icon name="refresh" size={13} /> Re-run
</Button>
</div>
<Tabs value={tab} onValueChange={onTab}>
<TabsList>
<TabsTrigger value="markdown">Markdown</TabsTrigger>
<TabsTrigger value="source">Source</TabsTrigger>
<TabsTrigger value="llms">
llms.txt{llmsFound ? '' : ' ·'}
</TabsTrigger>
<TabsTrigger value="robots">robots.txt</TabsTrigger>
<TabsTrigger value="meta">Meta</TabsTrigger>
<TabsTrigger value="checks">
Checks{counts.total > 0 ? ` (${counts.total})` : ''}
</TabsTrigger>
</TabsList>
<TabsContent value="markdown">
<div className="stack-sm">
<div className="flex-between muted agent-note">
<span>
{snapshot.markdown.length.toLocaleString()} characters ·{' '}
{snapshot.page ? `${snapshot.page.linkCount} links` : 'non-HTML document'} — links stay
in the viewer, like an agent re-entering a page.
</span>
{snapshot.markdownTruncated && <Badge variant="destructive">truncated</Badge>}
</div>
{snapshot.markdown.trim() === '' ? (
<Alert variant="warning" icon={<Icon name="warning" size={18} />}>
<AlertTitle>No content without JavaScript</AlertTitle>
<AlertDescription>
The server sent {snapshot.page ? `${snapshot.page.scriptCount} script tags and ` : ''}
no readable body text. An agent sees an empty page here.
</AlertDescription>
</Alert>
) : (
<Card className="agent-doc">
<MarkdownView source={snapshot.markdown} onNavigate={onNavigate} className="markdown-body" />
</Card>
)}
</div>
</TabsContent>
<TabsContent value="source">
<RawBlock
text={snapshot.markdown}
note={`${snapshot.markdown.length.toLocaleString()} characters of markdown`}
/>
</TabsContent>
<TabsContent value="llms">
<div className="stack-md">
<FileCard label="/llms.txt" file={files.llmsTxt} spec onNavigate={onNavigate} />
<FileCard label="/llms-full.txt" file={files.llmsFullTxt} onNavigate={onNavigate} />
<FileCard label="/.well-known/llms.txt" file={files.wellKnownLlmsTxt} onNavigate={onNavigate} />
</div>
</TabsContent>
<TabsContent value="robots">
<FileCard label="/robots.txt" file={files.robotsTxt} onNavigate={onNavigate} />
</TabsContent>
<TabsContent value="meta">
<MetaView snapshot={snapshot} />
</TabsContent>
<TabsContent value="checks">
<ChecksView checks={snapshot.checks} />
</TabsContent>
</Tabs>
</div>
)
}
/* ── Pieces ─────────────────────────────────────────────────────────── */
function StatusBadge({ status }: { status: number }) {
const variant = status < 300 ? 'positive' : status < 400 ? 'primary' : 'destructive'
return (
<Badge variant={variant}>
{status} {status < 300 ? 'OK' : status < 400 ? 'redirect' : 'error'}
</Badge>
)
}
function SeverityBadge({ severity }: { severity: Severity }) {
const variant: Record<Severity, 'positive' | 'secondary' | 'primary' | 'destructive'> = {
ok: 'positive',
info: 'secondary',
warn: 'primary',
error: 'destructive',
}
return <Badge variant={variant[severity]}>{severity}</Badge>
}
function countSeverities(checks: Check[]) {
const counts = { total: checks.length, warn: 0, error: 0 }
for (const check of checks) {
if (check.severity === 'warn') counts.warn++
if (check.severity === 'error') counts.error++
}
return counts
}
function RawBlock({ text, note }: { text: string; note: string }) {
const [copied, setCopied] = useState(false)
const copy = async () => {
try {
await navigator.clipboard.writeText(text)
setCopied(true)
setTimeout(() => setCopied(false), 1500)
} catch {
// clipboard unavailable (permissions); the text is selectable anyway
}
}
return (
<Card>
<div className="flex-between" style={{ padding: 'var(--space-3) var(--space-4)', borderBottom: '1px solid var(--border)' }}>
<span className="muted" style={{ fontSize: 'var(--text-xs)' }}>{note}</span>
<Button variant="ghost" onClick={copy}>
<Icon name={copied ? 'check' : 'copy'} size={13} /> {copied ? 'Copied' : 'Copy'}
</Button>
</div>
<pre className="agent-raw mono">{text}</pre>
</Card>
)
}
function FileCard({
label,
file,
spec,
onNavigate,
}: {
label: string
file: FileResult | null
spec?: boolean
onNavigate?: (href: string) => void
}) {
return (
<Card>
<CardHeader>
<div className="flex-between" style={{ flexWrap: 'wrap', gap: 'var(--space-2)' }}>
<div className="row-sm" style={{ alignItems: 'center' }}>
<Icon name="file" size={15} />
<CardTitle>{label}</CardTitle>
<FileBadge file={file} />
</div>
{file?.url && (
<a className="mono muted agent-file-url" href={file.url} target="_blank" rel="noreferrer">
{file.url} <Icon name="external-link" size={11} />
</a>
)}
</div>
<CardDescription>
{file === null
? 'Not fetched (the request failed before an answer).'
: spec
? 'The llmstxt.org convention: a curated, agent-first map of the site.'
: undefined}
</CardDescription>
</CardHeader>
{file?.status && file.status < 400 ? (
<div className="stack-sm">
{file.llmsDoc && <LlmsDocView doc={file.llmsDoc} onNavigate={onNavigate} />}
<details>
<summary className="muted" style={{ fontSize: 'var(--text-xs)', cursor: 'pointer' }}>
Raw {label}
</summary>
<pre className="agent-raw mono">{file.content}</pre>
</details>
</div>
) : (
<p className="muted" style={{ margin: 0, fontSize: 'var(--text-sm)' }}>
{file?.error
? `Unreachable: ${file.error}`
: file?.status
? `Not published (HTTP ${file.status}).${spec ? ' Agents fall back to parsing the HTML — adding one is the single highest-impact fix.' : ''}`
: 'No response recorded.'}
</p>
)}
</Card>
)
}
function FileBadge({ file }: { file: FileResult | null }) {
if (file === null) return <Badge variant="destructive">failed</Badge>
if (file.status < 300) return <Badge variant="positive">found</Badge>
if (file.status < 400) return <Badge variant="primary">redirect</Badge>
if (file.status === 404) return <Badge variant="secondary">missing</Badge>
return <Badge variant="destructive">HTTP {file.status}</Badge>
}
/** A parsed llms.txt: title, summary, and its curated sections. */
function LlmsDocView({ doc, onNavigate }: { doc: LlmsDoc; onNavigate?: (href: string) => void }) {
return (
<div className="agent-llms">
{doc.title && <h3 className="agent-llms-title">{doc.title}</h3>}
{doc.description && <blockquote className="agent-llms-quote">{doc.description}</blockquote>}
{doc.sections.map((section, index) => (
<div key={index} className="agent-llms-section">
{section.heading && <h4>{section.heading}</h4>}
{section.description && <p className="muted">{section.description}</p>}
<ul>
{section.links.map((link, linkIndex) => (
<li key={linkIndex}>
<a
href={link.url}
onClick={event => {
if (!onNavigate || !/^https?:\/\//i.test(link.url)) return
event.preventDefault()
onNavigate(link.url)
}}
>
{link.title || link.url}
</a>
{link.description && <span className="muted"> — {link.description}</span>}
</li>
))}
</ul>
</div>
))}
{doc.linkCount === 0 && <p className="muted">No links parsed from this file.</p>}
</div>
)
}
function MetaView({ snapshot }: { snapshot: Snapshot }) {
const page = snapshot.page
const rows: [string, string][] = page
? [
['Title', page.title || '—'],
['Description', page.description || '—'],
['Canonical', page.canonical || '—'],
['Meta robots', page.robotsMeta || 'not set'],
['Language', page.lang || '—'],
['H1', page.h1 || '—'],
['Links', `${page.linkCount} followable · ${page.deadLinks} dead`],
['Scripts', String(page.scriptCount)],
['Images', String(page.imageCount)],
['Visible text', `${page.textLength.toLocaleString()} chars · ${page.wordCount.toLocaleString()} words`],
]
: [['Content type', snapshot.contentType || '—']]
return (
<Card>
<dl className="agent-meta">
{rows.map(([term, value]) => (
<div key={term} className="agent-meta-row">
<dt className="muted">{term}</dt>
<dd>{value}</dd>
</div>
))}
</dl>
</Card>
)
}
function ChecksView({ checks }: { checks: Check[] }) {
const counts = countSeverities(checks)
return (
<Card>
<div className="row-sm agent-checks-summary" style={{ flexWrap: 'wrap' }}>
<Badge variant="destructive">{counts.error} errors</Badge>
<Badge variant="primary">{counts.warn} warnings</Badge>
<Badge variant="secondary">
{checks.filter(check => check.severity === 'info').length} notes
</Badge>
<Badge variant="positive">
{checks.filter(check => check.severity === 'ok').length} passed
</Badge>
</div>
<ul className="agent-checks">
{checks.map(check => (
<li key={check.id} className="agent-check">
<SeverityBadge severity={check.severity} />
<div>
<div className="agent-check-label">{check.label}</div>
<div className="agent-check-detail muted">{check.detail}</div>
</div>
</li>
))}
</ul>
</Card>
)
}
+2 -1
View File
@@ -99,7 +99,8 @@
}
/* ── Dark Palette (Exact lab() Specification) ────────────────── */
:root:not([data-theme='light']),
/* Dark theme — explicit selectors only. With no data-theme attribute the
:root default palette (identical to dark) already applies. */
:root[data-theme='dark'],
.dark {
color-scheme: dark;
+160
View File
@@ -0,0 +1,160 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { SWRConfig } from 'swr'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { AgentViewPage } from '../src/pages/AgentViewPage'
import { browse } from '../src/api/browse'
import type { Snapshot } from '../src/api/browse'
/* Page-level wiring: the api module is mocked, everything else (SWR, the
markdown renderer, tabs, the ?url= loop) is real. */
vi.mock('../src/api/browse', () => ({
browse: vi.fn(),
}))
const browseMock = vi.mocked(browse)
function snapshot(overrides: Partial<Snapshot> = {}): Snapshot {
return {
requestedUrl: 'https://example.test',
finalUrl: 'https://example.test/',
status: 200,
contentType: 'text/html; charset=utf-8',
durationMs: 210,
fetchedAt: '2026-09-16T10:00:00Z',
page: {
title: 'Example Site',
description: 'An example.',
canonical: '',
robotsMeta: '',
lang: 'en',
h1: 'Welcome',
linkCount: 1,
deadLinks: 0,
scriptCount: 1,
imageCount: 0,
wordCount: 12,
textLength: 80,
},
markdown: '# Welcome\n\nRead the [docs](https://example.test/docs).',
markdownTruncated: false,
files: {
llmsTxt: {
url: 'https://example.test/llms.txt',
status: 200,
contentType: 'text/markdown',
content: '# Example Site\n\n## Docs\n\n- [Guide](https://example.test/guide): the guide\n',
truncated: false,
llmsDoc: {
title: 'Example Site',
description: '',
sections: [
{
heading: 'Docs',
description: '',
links: [{ title: 'Guide', url: 'https://example.test/guide', description: 'the guide' }],
},
],
linkCount: 1,
},
},
llmsFullTxt: null,
wellKnownLlmsTxt: null,
robotsTxt: {
url: 'https://example.test/robots.txt',
status: 200,
contentType: 'text/plain',
content: 'User-agent: *\nAllow: /\n',
truncated: false,
},
},
checks: [
{ id: 'page.status', label: 'Page reachable', severity: 'ok', detail: '200 OK' },
{ id: 'llms.txt', label: '/llms.txt', severity: 'ok', detail: 'found' },
{ id: 'page.description', label: 'Meta description', severity: 'warn', detail: 'thin' },
],
...overrides,
}
}
function renderPage() {
return render(
<SWRConfig value={{ provider: () => new Map(), dedupingInterval: 0 }}>
<AgentViewPage />
</SWRConfig>,
)
}
beforeEach(() => {
window.history.replaceState(null, '', '/')
browseMock.mockReset()
})
afterEach(() => {
vi.clearAllMocks()
})
describe('AgentViewPage', () => {
it('stays on the explainer until a domain is submitted', () => {
browseMock.mockResolvedValue(snapshot())
renderPage()
expect(screen.getByText(/What does your site look like to an LLM/i)).toBeInTheDocument()
expect(browseMock).not.toHaveBeenCalled()
})
it('fetches the domain and renders the markdown with live links', async () => {
browseMock.mockResolvedValue(snapshot())
renderPage()
fireEvent.change(screen.getByLabelText(/domain or url/i), { target: { value: 'example.test' } })
fireEvent.click(screen.getByRole('button', { name: /view as agent/i }))
await waitFor(() => expect(browseMock).toHaveBeenCalledWith('example.test'))
expect(await screen.findByRole('heading', { level: 1, name: 'Welcome' })).toBeInTheDocument()
const link = screen.getByRole('link', { name: 'docs' })
expect(link).toHaveAttribute('href', 'https://example.test/docs')
expect(screen.getByText(/210 ms/i)).toBeInTheDocument()
})
it('re-enters the viewer when a markdown link is clicked', async () => {
browseMock.mockResolvedValue(snapshot())
renderPage()
fireEvent.change(screen.getByLabelText(/domain or url/i), { target: { value: 'example.test' } })
fireEvent.click(screen.getByRole('button', { name: /view as agent/i }))
fireEvent.click(await screen.findByRole('link', { name: 'docs' }))
await waitFor(() => expect(browseMock).toHaveBeenCalledWith('https://example.test/docs'))
})
it('shows the parsed llms.txt and the checks', async () => {
browseMock.mockResolvedValue(snapshot())
renderPage()
fireEvent.change(screen.getByLabelText(/domain or url/i), { target: { value: 'example.test' } })
fireEvent.click(screen.getByRole('button', { name: /view as agent/i }))
fireEvent.click(await screen.findByRole('tab', { name: /llms\.txt/i }))
expect((await screen.findAllByText('Example Site')).length).toBeGreaterThan(0)
expect(screen.getByText('Guide')).toBeInTheDocument()
fireEvent.click(screen.getByRole('tab', { name: /checks/i }))
expect(await screen.findByText('Page reachable')).toBeInTheDocument()
expect(screen.getByText(/warnings/)).toBeInTheDocument()
})
it('surfaces fetch failures with a retry', async () => {
browseMock.mockRejectedValue(
new (await import('../src/api/client')).ApiError(502, 'unavailable', 'could not fetch https://down.test/'),
)
renderPage()
fireEvent.change(screen.getByLabelText(/domain or url/i), { target: { value: 'down.test' } })
fireEvent.click(screen.getByRole('button', { name: /view as agent/i }))
expect(await screen.findByText(/the site did not answer/i)).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: /try again/i }))
await waitFor(() => expect(browseMock).toHaveBeenCalledTimes(2))
})
})
+107
View File
@@ -0,0 +1,107 @@
import { describe, expect, it } from 'vitest'
import { parseInline, parseMarkdown } from '../src/components/markdown/parse'
import type { InlineSpan } from '../src/components/markdown/parse'
/* Parser tests pin the shapes the Go converter emits — this renderer never
sees arbitrary markdown, so these cases are the whole contract. */
function text(spans: InlineSpan[]): string {
return spans.map(span => (span.kind === 'text' ? span.text : '')).join('')
}
describe('parseMarkdown blocks', () => {
it('parses headings, paragraphs and rules', () => {
const blocks = parseMarkdown('# Title\n\nBody text.\n\n---\n\n### Deep')
expect(blocks).toEqual([
{ kind: 'heading', level: 1, spans: [{ kind: 'text', text: 'Title' }] },
{ kind: 'paragraph', spans: [{ kind: 'text', text: 'Body text.' }] },
{ kind: 'hr' },
{ kind: 'heading', level: 3, spans: [{ kind: 'text', text: 'Deep' }] },
])
})
it('parses fenced code with language', () => {
const blocks = parseMarkdown('```go\nfmt.Println("hi")\n```')
expect(blocks).toEqual([{ kind: 'code', language: 'go', text: 'fmt.Println("hi")' }])
})
it('parses blockquotes by re-parsing their content', () => {
const blocks = parseMarkdown('> quoted\n> more')
expect(blocks).toEqual([
{
kind: 'quote',
blocks: [{ kind: 'paragraph', spans: [{ kind: 'text', text: 'quoted more' }] }],
},
])
})
it('parses flat and nested lists', () => {
const blocks = parseMarkdown('- one\n- two\n 1. inner\n3. three')
expect(blocks).toEqual([
{
kind: 'list',
ordered: false,
start: 1,
items: [
{ spans: [{ kind: 'text', text: 'one' }] },
{
spans: [{ kind: 'text', text: 'two' }],
sub: {
kind: 'list',
ordered: true,
start: 1,
items: [{ spans: [{ kind: 'text', text: 'inner' }] }],
},
},
],
},
{
kind: 'list',
ordered: true,
start: 3,
items: [{ spans: [{ kind: 'text', text: 'three' }] }],
},
])
})
it('parses GFM tables', () => {
const blocks = parseMarkdown('| A | B |\n| --- | --- |\n| 1 | 2 |')
expect(blocks).toEqual([{ kind: 'table', head: ['A', 'B'], rows: [['1', '2']] }])
})
})
describe('parseInline', () => {
it('splits links, code and emphasis', () => {
const spans = parseInline('see [Docs](https://x.test/d) and `code` plus **bold** *em* ~~gone~~')
expect(spans).toEqual([
{ kind: 'text', text: 'see ' },
{ kind: 'link', href: 'https://x.test/d', spans: [{ kind: 'text', text: 'Docs' }] },
{ kind: 'text', text: ' and ' },
{ kind: 'code', text: 'code' },
{ kind: 'text', text: ' plus ' },
{ kind: 'strong', spans: [{ kind: 'text', text: 'bold' }] },
{ kind: 'text', text: ' ' },
{ kind: 'em', spans: [{ kind: 'text', text: 'em' }] },
{ kind: 'text', text: ' ' },
{ kind: 'del', spans: [{ kind: 'text', text: 'gone' }] },
])
})
it('parses images with alt text', () => {
const spans = parseInline('![Logo](https://x.test/l.png)')
expect(spans).toEqual([{ kind: 'image', alt: 'Logo', src: 'https://x.test/l.png' }])
})
it('keeps link titles out of the href', () => {
const spans = parseInline('[API](https://x.test "the api")')
expect(spans).toEqual([
{ kind: 'link', href: 'https://x.test', spans: [{ kind: 'text', text: 'API' }] },
])
})
it('leaves plain text untouched', () => {
const spans = parseInline('just words')
expect(spans).toHaveLength(1)
expect(text(spans)).toBe('just words')
})
})