@hazem3500/palette 0.9.110 → 0.9.111

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/NODE.md CHANGED
@@ -166,3 +166,28 @@ To roll back, restore the backup. Redeploying the old image is not enough: the u
166
166
  The API's storage contract is `prepare().bind().first()/all()/run()` plus atomic `batch()`. `server/postgres.ts` implements it with a PostgreSQL pool, parameterized statements, and one transaction per batch. `server/postgres/001_initial.sql` mirrors the SQLite schema and its quota, revision, retention, and deletion triggers; later numbered files, such as `005_snapshots.sql` for element snapshots (`bytea`), mirror later D1 migrations. Add a numbered PostgreSQL migration whenever the D1 schema changes. Existing D1 migrations and the Worker entry point stay separate. The Node server runs the same request handler; transport and storage are the only new adapters.
167
167
 
168
168
  The Node process runs retention cleanup after requests, as the self-hosted Worker does. It handles `SIGTERM` by closing its HTTP listener and database pool. Database connection failures prevent startup; request errors return the existing API error format. Rate limits use the direct socket IP by default, and never trust a client's `X-Forwarded-For`. Behind trusted reverse proxies, set `PALETTE_TRUSTED_PROXY_HOPS` (older name `PALETTE_PROXY_HOPS`, 0 to 8) to the number of `X-Forwarded-For` entries those proxies append before the app. Most proxies append one; Google Cloud's external Application Load Balancer appends two (see [Deploy behind Google Cloud IAP](#deploy-behind-google-cloud-iap)). The client address is then that many entries from the right; entries further left are ignored. If the header has fewer entries, or that entry isn't an IP address, the socket IP is used. Check your load balancer’s forwarding behavior for the correct value, and only set it when requests can't bypass those proxies. Client-supplied `CF-Connecting-IP` is ignored. The Cloudflare Worker uses Cloudflare's `CF-Connecting-IP`.
169
+
170
+ ### Proxy access tokens (operator opt-in)
171
+
172
+ Proxy access tokens are off by default. To enable management and use, keep
173
+ `PALETTE_PROXY_AUTH=gcp-iap`, `PALETTE_PROXY_AUDIENCE` set to your IAP audience,
174
+ and your existing `PALETTE_AUTH_PATH_PREFIX` (for example `/backoffice`); additionally set
175
+ `PALETTE_PROXY_ACCESS_TOKENS=true` (Worker vars or Node environment). The legacy
176
+ `KOMO_PROXY_ACCESS_TOKENS` alias works when the Palette setting is unset.
177
+ Preserve your existing public path and proxy routing configuration.
178
+
179
+ Mint, list and revoke require a verified Google account's valid project session
180
+ issued through the proxy. A PAT cannot manage tokens. Mint requests must supply
181
+ `projects` as a nonempty explicit grant, `expiresInDays` from 1 to 365, and at
182
+ least one of `comments:read` / `comments:write`. The account UI offers these
183
+ constraints; the list response adds `policy: { explicitProjects: true,
184
+ maxLifetimeDays: 365 }`. `config.tokens` advertises the opt-in.
185
+
186
+ The operator opts into credentials that remain usable independently of IAP access
187
+ until they expire or are revoked; removing IAP access alone does not revoke a PAT.
188
+
189
+ Existing tokens with no expiry, a lifetime over 365 days, or no explicit project
190
+ grant are refused behind the proxy even after opt-in. Bounded, explicitly scoped
191
+ existing tokens remain usable. Nonproxy token policy is unchanged. Snapshot GET
192
+ requires `comments:read`, the granted project and an allowed Origin. No deployment
193
+ is enabled by this configuration example.
package/README.md CHANGED
@@ -924,3 +924,28 @@ To: #side > nav · index 0 · inside · component SideNav
924
924
  The hidden original's `display` is not proposed as a style change. The CLI reads these lines into `geometry` DesignEdit changes with `mode` (`size`, `flex-basis` or `span`), the values, `layout` and `effects`, and moves into `move` changes (`target`, `label`, `from` and `to` with `parent`, `index`, `position` and any `component`), telling agents to move the element's markup in source. Earlier proposals with `Move (translate)` or `Move (offset)` lines still parse and render as `translate`/`offset` changes; the current UI no longer produces free-form moves.
925
925
 
926
926
  Supported targets are HTML boxes and SVG roots in normal flow, flex or grid, or relatively, absolutely, fixed or sticky positioned, through positive axis-aligned 2D scale chains and normal, reflow or explicitly scaled Frames. Pointer deltas are converted to local CSS pixels, and scroll during pointer capture is accounted for. Only boxes that can't be previewed are excluded, each with its own reason in the panel and a **Select parent** action that picks the nearest resizable ancestor: inline text boxes, SVG shapes inside an SVG, `display: contents` or unrendered elements, table cells and rows, boxes split across lines or columns, rotated/skewed/3D/mirrored chains, and geometry animations in progress. A running animation only counts when it moves or sizes the box and Palette did not start it; it is named after 400ms, as a dot by the Layout title, so brief host transitions never show and the panel never reflows. This is deliberately not a universal layout editor.
927
+
928
+ ### Proxy access tokens (operator opt-in)
929
+
930
+ Proxy access tokens are off by default. To enable management and use, keep
931
+ `PALETTE_PROXY_AUTH=gcp-iap`, `PALETTE_PROXY_AUDIENCE` set to your IAP audience,
932
+ and your existing `PALETTE_AUTH_PATH_PREFIX` (for example `/backoffice`); additionally set
933
+ `PALETTE_PROXY_ACCESS_TOKENS=true` (Worker vars or Node environment). The legacy
934
+ `KOMO_PROXY_ACCESS_TOKENS` alias works when the Palette setting is unset.
935
+ Preserve your existing public path and proxy routing configuration.
936
+
937
+ Mint, list and revoke require a verified Google account's valid project session
938
+ issued through the proxy. A PAT cannot manage tokens. Mint requests must supply
939
+ `projects` as a nonempty explicit grant, `expiresInDays` from 1 to 365, and at
940
+ least one of `comments:read` / `comments:write`. The account UI offers these
941
+ constraints; the list response adds `policy: { explicitProjects: true,
942
+ maxLifetimeDays: 365 }`. `config.tokens` advertises the opt-in.
943
+
944
+ The operator opts into credentials that remain usable independently of IAP access
945
+ until they expire or are revoked; removing IAP access alone does not revoke a PAT.
946
+
947
+ Existing tokens with no expiry, a lifetime over 365 days, or no explicit project
948
+ grant are refused behind the proxy even after opt-in. Bounded, explicitly scoped
949
+ existing tokens remain usable. Nonproxy token policy is unchanged. Snapshot GET
950
+ requires `comments:read`, the granted project and an allowed Origin. No deployment
951
+ is enabled by this configuration example.
@@ -1 +1 @@
1
- import{a as e}from"./chunk-DU7H4TEX.js";import"./chunk-5HZI7HRR.js";import{E as t,F as n,G as o,y as a}from"./chunk-4HBBEVFK.js";var i=864e5,r=[["7 days",7],["30 days",30],["90 days",90],["1 year",365],["Never",null]],s={"comments:read":"Read","comments:write":"Write"},p=[["year",365*i],["month",30*i],["week",7*i],["day",i],["hour",36e5],["minute",6e4]];function d(e,t=Date.now()){let n=e-t,o=new Intl.RelativeTimeFormat(void 0,{numeric:"auto",style:"short"});for(let[e,t]of p)if(Math.abs(n)>=t)return o.format(Math.round(n/t),e);return"just now"}var l=e=>new Intl.DateTimeFormat(void 0,{dateStyle:"medium",timeStyle:"short"}).format(e),c=0;function u(i,p,u){let k=t("div","account account-tokens");k.setAttribute("role","region");let f=t("style");f.textContent=".account-layer .account.account-tokens{padding-top:16px;gap:16px}\n.api-tokens-head{display:flex;align-items:center;gap:6px;margin-right:36px;min-height:30px}\n.api-tokens-head h2{margin:0;font-size:15px;font-weight:500;letter-spacing:-.01em}\n.api-tokens-head h2:focus{outline:none}\n.account-layer .account .api-tokens-back{border-radius:8px;color:#bbb}\n.api-tokens-back svg{rotate:90deg;width:16px;height:16px}\n.api-tokens-back:hover{background:color-mix(in oklab,var(--mm-ink) 7%,transparent);color:var(--mm-ink)}\n.api-tokens-lede,.api-tokens-note{margin:0;font-size:12px;line-height:1.5;color:#999}\n.api-tokens-note code,.api-token-id{font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#aaa;overflow-wrap:anywhere}\n.api-tokens-list{list-style:none;margin:0;padding:0;display:grid;gap:8px}\n.api-token,.api-tokens-sheet,.api-tokens-empty{display:grid;gap:8px;padding:14px;border-radius:14px;background:#ffffff05;box-shadow:inset 0 0 0 1px #ffffff09;font-size:12px;color:#ddd}\n.api-tokens-empty{gap:4px;color:#999;text-align:center;padding:20px 14px}\n.api-tokens-empty strong{color:#ddd;font-weight:500}\n.api-token-row{display:flex;align-items:center;gap:8px;min-width:0}\n.api-token-row strong{font-weight:500;color:#eee;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}\n.api-token-chips{display:flex;flex-wrap:wrap;gap:4px}\n.api-token-chip{padding:0 6px;border-radius:5px;background:color-mix(in oklab,var(--mm-ink) 9%,transparent);color:color-mix(in oklab,var(--mm-ink) 78%,transparent);font-size:10px;font-weight:500;line-height:18px;white-space:nowrap}\n.api-token-chip[data-tone=warn]{background:#f1b77d1f;color:#f1b77d}\n.api-token-meta{display:flex;flex-wrap:wrap;gap:2px 10px;margin:0;color:#909090;font-size:11px}\n.api-token-meta div{display:flex;gap:4px}\n.api-token-meta dd{margin:0;color:#bbb}\n.api-token-meta time{cursor:default}\n.account-layer .account .api-token-revoke{margin-left:auto;padding:4px 8px;min-height:26px;border-radius:7px;font-size:11px;background:transparent;color:#fda29b}\n.api-token-revoke:hover,.api-tokens-actions .api-tokens-danger:hover{background:#fda29b14}\n.api-token-confirm{display:grid;gap:8px;padding-top:8px;box-shadow:0 -1px #ffffff0b}\n.api-token-confirm p,.api-tokens-sheet p{margin:0;line-height:1.5;color:#bbb}\n.api-tokens-actions{display:flex;justify-content:flex-end;gap:8px}\n.account-layer .account .api-tokens-actions button{width:auto;min-height:32px;padding:6px 12px;border-radius:9px;font-size:12px}\n.account-layer .account .api-tokens-actions .api-tokens-danger{background:#fda29b1a;color:#fda29b}\n.api-tokens-sheet{gap:14px;padding:16px}\n.api-tokens-sheet h3{margin:0;font-size:13px;font-weight:500;color:#eee}\n.api-tokens-sheet fieldset{border:0;margin:0;padding:0;display:grid;gap:8px;min-width:0}\n.api-tokens-sheet legend{padding:0;margin-bottom:8px;font-size:12px;color:#999}\n.account-layer .account .api-tokens-sheet input[type=text]{min-height:38px;padding:8px 10px;font-size:13px;border-radius:10px}\n.api-tokens-choices{display:flex;flex-wrap:wrap;gap:6px}\n.api-tokens-choice{position:relative;display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:9px;background:#ffffff06;box-shadow:inset 0 0 0 1px #ffffff0b;color:#bbb;cursor:pointer;font-size:12px;line-height:16px}\n.api-tokens-choice:hover{color:#eee}\n.account-layer .account .api-tokens-choice input{position:absolute;inset:0;width:100%;height:100%;min-height:0;margin:0;opacity:0;cursor:pointer}\n.api-tokens-choice:has(input:checked){background:#bda6ef24;box-shadow:inset 0 0 0 1px #bda6ef80;color:#eee}\n.api-tokens-choice:has(input:focus-visible){outline:2px solid #bda6ef;outline-offset:2px}\n.api-tokens-choice small{color:#888;font-size:10px}\n.api-tokens-sheet p.api-tokens-error,.api-tokens-error{margin:0;color:#fda29b;font-size:12px;line-height:1.5}\n.api-tokens-error:empty{display:none}\n.api-tokens-secret{display:flex;gap:8px;align-items:center}\n.api-tokens-secret code{flex:1;min-width:0;padding:8px 10px;border-radius:10px;background:#00000040;box-shadow:inset 0 0 0 1px #ffffff0b;font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#eee;overflow-wrap:anywhere;user-select:all}\n.account-layer .account .api-tokens-secret button{width:auto;min-height:34px;padding:0 12px;font-size:12px}\n.api-tokens-secret svg{width:14px;height:14px}\n.api-tokens-warning{display:flex;gap:7px;align-items:flex-start}\n.api-tokens-warning svg{flex:none;width:13px;height:13px;margin-top:2px;color:#f1b77d}\n.api-tokens-skeleton{height:84px;border-radius:14px;background:linear-gradient(90deg,#ffffff05,#ffffff0d,#ffffff05) 0 0/200% 100%;animation:api-tokens-shimmer 1.4s linear infinite}\n@keyframes api-tokens-shimmer{to{background-position:-200% 0}}\n@media (prefers-reduced-motion:reduce){.api-tokens-skeleton{animation:none}}";let h=t("div","api-tokens-head"),m=o("Back to account",()=>z(),"icon api-tokens-back","chevron"),g=t("h2","","API tokens");g.id="api-tokens-"+ ++c,g.tabIndex=-1,g.dataset.focusKey="api-tokens-title",k.setAttribute("aria-labelledby",g.id),h.append(m,g);let x=t("p","api-tokens-lede","Let bots and CI read or write comments as you. Treat tokens like passwords."),y=t("p","sr-only");y.setAttribute("role","status"),y.setAttribute("aria-live","polite");let b=t("div"),v=t("div"),w=t("div","morphing-menu__tooltip sidebar-tooltip");w.setAttribute("aria-hidden","true"),k.append(f,h,x,b,v,y,w),m.dataset.focusKey="api-tokens-back";let A=null,C=null,T=!1,$=!1,E="",j=null,S=new Map,N=e=>{y.textContent="",setTimeout(()=>y.textContent=e,30)},I=e=>{a()||e.animate([{opacity:0,transform:"translateY(4px)"},{opacity:1,transform:"none"}],{duration:250,easing:"cubic-bezier(.22,1,.36,1)"})},K=e=>requestAnimationFrame(()=>k.querySelector(`[data-focus-key="${e}"]`)?.focus()),R=t=>t instanceof e&&404===t.status?"This Palette server doesn’t support API tokens.":t instanceof Error?t.message:"Something went wrong. Try again.";k.focusStart=()=>K("api-tokens-title"),k.dispose=()=>{q(),w.remove()};let z=()=>{k.dispose(),u()};k.addEventListener("keydown",e=>{if("Escape"===e.key)if(e.stopPropagation(),e.preventDefault(),E){let e=E;E="",B(),K(`api-tokens-revoke-${e}`)}else j?K("api-tokens-done"):T?H():z()});let L=0,q=()=>{clearTimeout(L),delete w.dataset.open};k.addEventListener("pointerover",e=>{let t=e.target.closest?.("[data-tip]");if(!t)return q();clearTimeout(L),L=window.setTimeout(()=>{if(!t.isConnected)return;let e=k.getRootNode();w.parentNode!==e&&e instanceof ShadowRoot&&e.append(w);let n=t.getBoundingClientRect();w.textContent=t.dataset.tip??"";let o=w.offsetWidth/2;w.style.left=`${Math.max(8+o,Math.min(document.documentElement.clientWidth-8-o,n.left+n.width/2))}px`,w.style.top=n.top-8-w.offsetHeight+"px",w.dataset.open="true"},w.dataset.open?0:400)}),k.addEventListener("pointerleave",k.dispose),k.addEventListener("pointerdown",k.dispose);let D=(e,n,o)=>{let a=t("div"),i=t("dd");if(null===n)i.textContent=o;else{let e=t("time","",d(n));e.dateTime=new Date(n).toISOString(),e.dataset.tip=l(n),i.append(e,t("span","sr-only",` (${l(n)})`))}return a.append(t("dt","",e),i),a},M=(e,n,o)=>{let a=t("span","api-token-chip",e);return n&&(a.dataset.tone=n),o&&(a.dataset.tip=o),a},P=e=>e?1===e.length&&e[0]===p?"This project":1===e.length?e[0]:`${e.length} projects`:"All projects";function F(n){let a=t("li","api-token");a.dataset.token=n.id;let r=t("div","api-token-row"),p=t("strong","",n.name);p.id=`api-token-${n.id}-${g.id}`,a.setAttribute("aria-labelledby",p.id),r.append(p),n.expired&&r.append(M("Expired","warn"));let d=o(`Revoke ${n.name}`,()=>{E=n.id,S.delete(n.id),B(),K(`api-tokens-keep-${n.id}`)},"api-token-revoke");d.textContent="Revoke",d.removeAttribute("title"),d.dataset.focusKey=`api-tokens-revoke-${n.id}`,d.disabled=E===n.id,r.append(d);let l=t("div","api-token-chips");for(let e of n.scopes)l.append(M(s[e]??e,"",e));l.append(M(P(n.projects),"",n.projects?.join(", ")??"Every project you can access"));let c=t("dl","api-token-meta");c.append(D("Created",n.createdAt,""),D("Last used",n.lastUsedAt,"Never"),D(n.expired?"Expired":"Expires",n.expiresAt,"Never")),a.append(r,t("code","api-token-id",`${n.prefix}…${n.last4}`),l,c);let u=S.get(n.id);if(u){let e=t("p","api-tokens-error",u);e.setAttribute("role","alert"),a.append(e)}if(E===n.id){let r=t("div","api-token-confirm");r.setAttribute("role","group"),r.setAttribute("aria-label",`Revoke ${n.name}`);let s=t("div","api-tokens-actions"),p=o("Keep token",()=>{E="",B(),K(`api-tokens-revoke-${n.id}`)},"secondary");p.dataset.focusKey=`api-tokens-keep-${n.id}`;let d=o("Revoke token",()=>{!async function(t){if(!A)return;let n=A,o=n.tokens.findIndex(e=>e.id===t.id);if(o<0)return;n.tokens.splice(o,1),E="",B();let a=n.tokens[o]??n.tokens[o-1];K(a?`api-tokens-revoke-${a.id}`:"api-tokens-new");try{await i.request(`account/tokens/${encodeURIComponent(t.id)}`,"DELETE"),N(`Revoked ${t.name}.`)}catch(a){if(a instanceof e&&404===a.status)return void N(`Revoked ${t.name}.`);if(A!==n)return;n.tokens.splice(Math.min(o,n.tokens.length),0,t),S.set(t.id,`Couldn’t revoke: ${R(a)}`),B(),N(`Couldn’t revoke ${t.name}.`),K(`api-tokens-revoke-${t.id}`)}}(n)},"secondary api-tokens-danger");d.dataset.focusKey=`api-tokens-confirm-${n.id}`,s.append(p,d),r.append(t("p","","Anything using this token stops working right away. This can’t be undone."),s),a.append(r),I(r)}return a}function H(){T=!1,B(),K("api-tokens-new")}let Y="";function B(){q();let a=A?j?"secret":T?"form":"list":C?"error":"loading",d=[];if(A&&"list"===a){let e=o("New token",()=>{T=!0,B(),K("api-tokens-name")},"secondary");e.replaceChildren(n("plus"),document.createTextNode("New token")),e.removeAttribute("title"),e.dataset.focusKey="api-tokens-new",d.push(e)}else A&&"form"===a?d.push(function(n){let a=t("form","api-tokens-sheet");a.noValidate=!0,a.setAttribute("aria-labelledby",`${g.id}-new`);let d=t("h3","","New token");d.id=`${g.id}-new`;let l=t("label","account-name-label","Name"),c=t("input");c.type="text",c.maxLength=60,c.required=!0,c.autocomplete="off",c.placeholder="e.g. Deploy bot",c.dataset.focusKey="api-tokens-name",l.append(c);let u=(e,n,o,a)=>{let i=t("fieldset"),r=t("div","api-tokens-choices");for(let[e,i,s,p]of a){let a=t("label","api-tokens-choice",i),d=t("input");d.type=n,d.name=o,d.value=e,d.checked=s,d.dataset.focusKey=`api-tokens-${o}-${e}`,p&&a.append(t("small","",p)),a.prepend(d),r.append(a)}return i.append(t("legend","",e),r),i},k=u("Access","checkbox","scope",n.scopes.map(e=>[e,s[e]?`${s[e]} comments`:e,"comments:read"===e])),f=n.projects.includes(p),h=u("Projects","radio","projects",[...f?[["this","This project",!0]]:[],["all","All your projects",!f]]),m=u("Expires","radio","expiry",r.map(([e,t])=>[String(t),e,30===t])),x=t("p","api-tokens-error");x.setAttribute("role","alert");let y=t("div","api-tokens-actions"),b=o("Cancel",H,"secondary");b.dataset.focusKey="api-tokens-cancel";let v=o("Create token",()=>{},"primary");return v.type="submit",v.dataset.focusKey="api-tokens-create",y.append(b,v),a.append(d,l,k,h,m,x,y),a.addEventListener("submit",async t=>{if(t.preventDefault(),$)return;let o=[...a.querySelectorAll("input[name=scope]:checked")].map(e=>e.value),r=a.querySelector("input[name=expiry]:checked")?.value,s=c.value.trim()?o.length?null:k.querySelector("input"):c;if(x.textContent=c.value.trim()?o.length?"":"Choose read, write, or both.":"Name your token.",s)s.focus();else{$=!0,v.disabled=b.disabled=!0,v.setAttribute("aria-busy","true");try{let e=await i.request("account/tokens","POST",{name:c.value.trim(),scopes:o,expiresInDays:r&&"null"!==r?Number(r):null,projects:"this"===a.querySelector("input[name=projects]:checked")?.value?[p]:[]});if(A!==n)return;n.tokens.unshift(e.item),T=!1,j=e,B(),N("Token created. Copy it now. You won’t see it again."),K("api-tokens-copy")}catch(t){x.textContent=t instanceof e&&429===t.status?"You’ve created several tokens recently. Try again later.":R(t),v.focus()}finally{$=!1,v.disabled=b.disabled=!1,v.removeAttribute("aria-busy")}}}),a}(A)):j&&d.push(function(e){let a=t("section","api-tokens-sheet");a.setAttribute("aria-labelledby",`${g.id}-secret`);let i=t("h3","",`Copy “${e.item.name}”`);i.id=`${g.id}-secret`;let r=t("p","api-tokens-warning");r.append(n("info"),t("span","","You won’t see this token again. Store it in a secret manager, never in source control."));let s=t("div","api-tokens-secret"),p=t("code","",e.token);p.setAttribute("aria-label","New API token"),p.tabIndex=0,p.dataset.focusKey="api-tokens-secret";let d=o("Copy token",async()=>{try{await navigator.clipboard.writeText(e.token),d.replaceChildren(n("check"),document.createTextNode("Copied")),N("Token copied to the clipboard.")}catch{getSelection()?.selectAllChildren(p),N("Couldn’t copy. The token is selected; copy it with your keyboard.")}},"primary");d.replaceChildren(n("copy"),document.createTextNode("Copy")),d.removeAttribute("title"),d.dataset.focusKey="api-tokens-copy",s.append(p,d);let l=t("div","api-tokens-actions"),c=o("Done",()=>{j=null,B(),K("api-tokens-new")},"secondary");return c.dataset.focusKey="api-tokens-done",l.append(c),a.append(i,r,s,l),a}(j));if(a!==Y&&d[0]&&I(d[0]),Y=a,b.replaceChildren(...d),v.setAttribute("aria-busy",String(!A&&!C)),!A){if(C){let n=t("p","api-tokens-error",R(C));n.setAttribute("role","alert");let a=C instanceof e&&[403,404].includes(C.status),i=o("Try again",()=>{U()},"secondary");i.dataset.focusKey="api-tokens-retry",v.replaceChildren(n,...a?[]:[i])}else{let e=t("div","api-tokens-list");e.setAttribute("aria-hidden","true"),e.append(t("div","api-tokens-skeleton"),t("div","api-tokens-skeleton"));let n=t("span","sr-only","Loading API tokens");n.setAttribute("role","status"),v.replaceChildren(e,n)}return}if(!A.tokens.length){let e=t("p","api-tokens-empty");return e.append(t("strong","","No tokens yet"),t("span","","Create one for a bot, a script, or a CI job.")),void v.replaceChildren(e)}let l=t("ul","api-tokens-list");l.setAttribute("aria-label","Your API tokens"),l.append(...A.tokens.map(F));let c=t("p","api-tokens-note");c.append(document.createTextNode("Use a token as "),t("code","","PALETTE_TOKEN"),document.createTextNode(" with the palette CLI.")),v.replaceChildren(l,c)}async function U(){C=null,A=null,B();try{let t=await i.request("account/tokens");if(!Array.isArray(t.tokens)||!Array.isArray(t.projects))throw new e(0,"Couldn’t read your tokens. Try again.");A={tokens:t.tokens,projects:t.projects,scopes:Array.isArray(t.scopes)?t.scopes:["comments:read","comments:write"]}}catch(e){C=e instanceof Error?e:new Error(String(e))}B()}return U(),k}export{u as accountTokens,d as relativeTime};
1
+ import{a as e}from"./chunk-DU7H4TEX.js";import"./chunk-5HZI7HRR.js";import{E as t,F as n,G as o,y as a}from"./chunk-4HBBEVFK.js";var i=864e5,r=[["7 days",7],["30 days",30],["90 days",90],["1 year",365],["Never",null]],s={"comments:read":"Read","comments:write":"Write"},p=[["year",365*i],["month",30*i],["week",7*i],["day",i],["hour",36e5],["minute",6e4]];function d(e,t=Date.now()){let n=e-t,o=new Intl.RelativeTimeFormat(void 0,{numeric:"auto",style:"short"});for(let[e,t]of p)if(Math.abs(n)>=t)return o.format(Math.round(n/t),e);return"just now"}var l=e=>new Intl.DateTimeFormat(void 0,{dateStyle:"medium",timeStyle:"short"}).format(e),c=0;function u(i,p,u){let k=t("div","account account-tokens");k.setAttribute("role","region");let f=t("style");f.textContent=".account-layer .account.account-tokens{padding-top:16px;gap:16px}\n.api-tokens-head{display:flex;align-items:center;gap:6px;margin-right:36px;min-height:30px}\n.api-tokens-head h2{margin:0;font-size:15px;font-weight:500;letter-spacing:-.01em}\n.api-tokens-head h2:focus{outline:none}\n.account-layer .account .api-tokens-back{border-radius:8px;color:#bbb}\n.api-tokens-back svg{rotate:90deg;width:16px;height:16px}\n.api-tokens-back:hover{background:color-mix(in oklab,var(--mm-ink) 7%,transparent);color:var(--mm-ink)}\n.api-tokens-lede,.api-tokens-note{margin:0;font-size:12px;line-height:1.5;color:#999}\n.api-tokens-note code,.api-token-id{font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#aaa;overflow-wrap:anywhere}\n.api-tokens-list{list-style:none;margin:0;padding:0;display:grid;gap:8px}\n.api-token,.api-tokens-sheet,.api-tokens-empty{display:grid;gap:8px;padding:14px;border-radius:14px;background:#ffffff05;box-shadow:inset 0 0 0 1px #ffffff09;font-size:12px;color:#ddd}\n.api-tokens-empty{gap:4px;color:#999;text-align:center;padding:20px 14px}\n.api-tokens-empty strong{color:#ddd;font-weight:500}\n.api-token-row{display:flex;align-items:center;gap:8px;min-width:0}\n.api-token-row strong{font-weight:500;color:#eee;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}\n.api-token-chips{display:flex;flex-wrap:wrap;gap:4px}\n.api-token-chip{padding:0 6px;border-radius:5px;background:color-mix(in oklab,var(--mm-ink) 9%,transparent);color:color-mix(in oklab,var(--mm-ink) 78%,transparent);font-size:10px;font-weight:500;line-height:18px;white-space:nowrap}\n.api-token-chip[data-tone=warn]{background:#f1b77d1f;color:#f1b77d}\n.api-token-meta{display:flex;flex-wrap:wrap;gap:2px 10px;margin:0;color:#909090;font-size:11px}\n.api-token-meta div{display:flex;gap:4px}\n.api-token-meta dd{margin:0;color:#bbb}\n.api-token-meta time{cursor:default}\n.account-layer .account .api-token-revoke{margin-left:auto;padding:4px 8px;min-height:26px;border-radius:7px;font-size:11px;background:transparent;color:#fda29b}\n.api-token-revoke:hover,.api-tokens-actions .api-tokens-danger:hover{background:#fda29b14}\n.api-token-confirm{display:grid;gap:8px;padding-top:8px;box-shadow:0 -1px #ffffff0b}\n.api-token-confirm p,.api-tokens-sheet p{margin:0;line-height:1.5;color:#bbb}\n.api-tokens-actions{display:flex;justify-content:flex-end;gap:8px}\n.account-layer .account .api-tokens-actions button{width:auto;min-height:32px;padding:6px 12px;border-radius:9px;font-size:12px}\n.account-layer .account .api-tokens-actions .api-tokens-danger{background:#fda29b1a;color:#fda29b}\n.api-tokens-sheet{gap:14px;padding:16px}\n.api-tokens-sheet h3{margin:0;font-size:13px;font-weight:500;color:#eee}\n.api-tokens-sheet fieldset{border:0;margin:0;padding:0;display:grid;gap:8px;min-width:0}\n.api-tokens-sheet legend{padding:0;margin-bottom:8px;font-size:12px;color:#999}\n.account-layer .account .api-tokens-sheet input[type=text]{min-height:38px;padding:8px 10px;font-size:13px;border-radius:10px}\n.api-tokens-choices{display:flex;flex-wrap:wrap;gap:6px}\n.api-tokens-choice{position:relative;display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:9px;background:#ffffff06;box-shadow:inset 0 0 0 1px #ffffff0b;color:#bbb;cursor:pointer;font-size:12px;line-height:16px}\n.api-tokens-choice:hover{color:#eee}\n.account-layer .account .api-tokens-choice input{position:absolute;inset:0;width:100%;height:100%;min-height:0;margin:0;opacity:0;cursor:pointer}\n.api-tokens-choice:has(input:checked){background:#bda6ef24;box-shadow:inset 0 0 0 1px #bda6ef80;color:#eee}\n.api-tokens-choice:has(input:focus-visible){outline:2px solid #bda6ef;outline-offset:2px}\n.api-tokens-choice small{color:#888;font-size:10px}\n.api-tokens-sheet p.api-tokens-error,.api-tokens-error{margin:0;color:#fda29b;font-size:12px;line-height:1.5}\n.api-tokens-error:empty{display:none}\n.api-tokens-secret{display:flex;gap:8px;align-items:center}\n.api-tokens-secret code{flex:1;min-width:0;padding:8px 10px;border-radius:10px;background:#00000040;box-shadow:inset 0 0 0 1px #ffffff0b;font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#eee;overflow-wrap:anywhere;user-select:all}\n.account-layer .account .api-tokens-secret button{width:auto;min-height:34px;padding:0 12px;font-size:12px}\n.api-tokens-secret svg{width:14px;height:14px}\n.api-tokens-warning{display:flex;gap:7px;align-items:flex-start}\n.api-tokens-warning svg{flex:none;width:13px;height:13px;margin-top:2px;color:#f1b77d}\n.api-tokens-skeleton{height:84px;border-radius:14px;background:linear-gradient(90deg,#ffffff05,#ffffff0d,#ffffff05) 0 0/200% 100%;animation:api-tokens-shimmer 1.4s linear infinite}\n@keyframes api-tokens-shimmer{to{background-position:-200% 0}}\n@media (prefers-reduced-motion:reduce){.api-tokens-skeleton{animation:none}}";let m=t("div","api-tokens-head"),h=o("Back to account",()=>z(),"icon api-tokens-back","chevron"),g=t("h2","","API tokens");g.id="api-tokens-"+ ++c,g.tabIndex=-1,g.dataset.focusKey="api-tokens-title",k.setAttribute("aria-labelledby",g.id),m.append(h,g);let x=t("p","api-tokens-lede","Let bots and CI read or write comments as you. Treat tokens like passwords."),y=t("p","sr-only");y.setAttribute("role","status"),y.setAttribute("aria-live","polite");let b=t("div"),v=t("div"),w=t("div","morphing-menu__tooltip sidebar-tooltip");w.setAttribute("aria-hidden","true"),k.append(f,m,x,b,v,y,w),h.dataset.focusKey="api-tokens-back";let A=null,C=null,T=!1,$=!1,j="",E=null,S=new Map,N=e=>{y.textContent="",setTimeout(()=>y.textContent=e,30)},I=e=>{a()||e.animate([{opacity:0,transform:"translateY(4px)"},{opacity:1,transform:"none"}],{duration:250,easing:"cubic-bezier(.22,1,.36,1)"})},K=e=>requestAnimationFrame(()=>k.querySelector(`[data-focus-key="${e}"]`)?.focus()),R=t=>t instanceof e&&404===t.status?"This Palette server doesn’t support API tokens.":t instanceof Error?t.message:"Something went wrong. Try again.";k.focusStart=()=>K("api-tokens-title"),k.dispose=()=>{P(),w.remove()};let z=()=>{k.dispose(),u()};k.addEventListener("keydown",e=>{if("Escape"===e.key)if(e.stopPropagation(),e.preventDefault(),j){let e=j;j="",B(),K(`api-tokens-revoke-${e}`)}else E?K("api-tokens-done"):T?H():z()});let L=0,P=()=>{clearTimeout(L),delete w.dataset.open};k.addEventListener("pointerover",e=>{let t=e.target.closest?.("[data-tip]");if(!t)return P();clearTimeout(L),L=window.setTimeout(()=>{if(!t.isConnected)return;let e=k.getRootNode();w.parentNode!==e&&e instanceof ShadowRoot&&e.append(w);let n=t.getBoundingClientRect();w.textContent=t.dataset.tip??"";let o=w.offsetWidth/2;w.style.left=`${Math.max(8+o,Math.min(document.documentElement.clientWidth-8-o,n.left+n.width/2))}px`,w.style.top=n.top-8-w.offsetHeight+"px",w.dataset.open="true"},w.dataset.open?0:400)}),k.addEventListener("pointerleave",k.dispose),k.addEventListener("pointerdown",k.dispose);let q=(e,n,o)=>{let a=t("div"),i=t("dd");if(null===n)i.textContent=o;else{let e=t("time","",d(n));e.dateTime=new Date(n).toISOString(),e.dataset.tip=l(n),i.append(e,t("span","sr-only",` (${l(n)})`))}return a.append(t("dt","",e),i),a},D=(e,n,o)=>{let a=t("span","api-token-chip",e);return n&&(a.dataset.tone=n),o&&(a.dataset.tip=o),a},M=e=>e?1===e.length&&e[0]===p?"This project":1===e.length?e[0]:`${e.length} projects`:"All projects";function F(n){let a=t("li","api-token");a.dataset.token=n.id;let r=t("div","api-token-row"),p=t("strong","",n.name);p.id=`api-token-${n.id}-${g.id}`,a.setAttribute("aria-labelledby",p.id),r.append(p),n.expired&&r.append(D("Expired","warn"));let d=o(`Revoke ${n.name}`,()=>{j=n.id,S.delete(n.id),B(),K(`api-tokens-keep-${n.id}`)},"api-token-revoke");d.textContent="Revoke",d.removeAttribute("title"),d.dataset.focusKey=`api-tokens-revoke-${n.id}`,d.disabled=j===n.id,r.append(d);let l=t("div","api-token-chips");for(let e of n.scopes)l.append(D(s[e]??e,"",e));l.append(D(M(n.projects),"",n.projects?.join(", ")??"Every project you can access"));let c=t("dl","api-token-meta");c.append(q("Created",n.createdAt,""),q("Last used",n.lastUsedAt,"Never"),q(n.expired?"Expired":"Expires",n.expiresAt,"Never")),a.append(r,t("code","api-token-id",`${n.prefix}…${n.last4}`),l,c);let u=S.get(n.id);if(u){let e=t("p","api-tokens-error",u);e.setAttribute("role","alert"),a.append(e)}if(j===n.id){let r=t("div","api-token-confirm");r.setAttribute("role","group"),r.setAttribute("aria-label",`Revoke ${n.name}`);let s=t("div","api-tokens-actions"),p=o("Keep token",()=>{j="",B(),K(`api-tokens-revoke-${n.id}`)},"secondary");p.dataset.focusKey=`api-tokens-keep-${n.id}`;let d=o("Revoke token",()=>{!async function(t){if(!A)return;let n=A,o=n.tokens.findIndex(e=>e.id===t.id);if(o<0)return;n.tokens.splice(o,1),j="",B();let a=n.tokens[o]??n.tokens[o-1];K(a?`api-tokens-revoke-${a.id}`:"api-tokens-new");try{await i.request(`account/tokens/${encodeURIComponent(t.id)}`,"DELETE"),N(`Revoked ${t.name}.`)}catch(a){if(a instanceof e&&404===a.status)return void N(`Revoked ${t.name}.`);if(A!==n)return;n.tokens.splice(Math.min(o,n.tokens.length),0,t),S.set(t.id,`Couldn’t revoke: ${R(a)}`),B(),N(`Couldn’t revoke ${t.name}.`),K(`api-tokens-revoke-${t.id}`)}}(n)},"secondary api-tokens-danger");d.dataset.focusKey=`api-tokens-confirm-${n.id}`,s.append(p,d),r.append(t("p","","Anything using this token stops working right away. This can’t be undone."),s),a.append(r),I(r)}return a}function H(){T=!1,B(),K("api-tokens-new")}let Y="";function B(){P();let a=A?E?"secret":T?"form":"list":C?"error":"loading",d=[];if(A&&"list"===a){let e=o("New token",()=>{T=!0,B(),K("api-tokens-name")},"secondary");e.replaceChildren(n("plus"),document.createTextNode("New token")),e.removeAttribute("title"),e.dataset.focusKey="api-tokens-new",d.push(e)}else A&&"form"===a?d.push(function(n){let a=t("form","api-tokens-sheet");a.noValidate=!0,a.setAttribute("aria-labelledby",`${g.id}-new`);let d=t("h3","","New token");d.id=`${g.id}-new`;let l=t("label","account-name-label","Name"),c=t("input");c.type="text",c.maxLength=60,c.required=!0,c.autocomplete="off",c.placeholder="e.g. Deploy bot",c.dataset.focusKey="api-tokens-name",l.append(c);let u=(e,n,o,a)=>{let i=t("fieldset"),r=t("div","api-tokens-choices");for(let[e,i,s,p]of a){let a=t("label","api-tokens-choice",i),d=t("input");d.type=n,d.name=o,d.value=e,d.checked=s,d.dataset.focusKey=`api-tokens-${o}-${e}`,p&&a.append(t("small","",p)),a.prepend(d),r.append(a)}return i.append(t("legend","",e),r),i},k=u("Access","checkbox","scope",n.scopes.map(e=>[e,s[e]?`${s[e]} comments`:e,"comments:read"===e])),f=n.projects.includes(p),m=u("Projects","radio","projects",n.policy?.explicitProjects?n.projects.map(e=>[e,e===p?"This project":e,e===(f?p:n.projects[0])]):[...f?[["this","This project",!0]]:[],["all","All your projects",!f]]),h=u("Expires","radio","expiry",r.filter(([,e])=>!n.policy||null!==e&&e<=n.policy.maxLifetimeDays).map(([e,t])=>[String(t),e,30===t])),x=t("p","api-tokens-error");x.setAttribute("role","alert");let y=t("div","api-tokens-actions"),b=o("Cancel",H,"secondary");b.dataset.focusKey="api-tokens-cancel";let v=o("Create token",()=>{},"primary");return v.type="submit",v.dataset.focusKey="api-tokens-create",y.append(b,v),a.append(d,l,k,m,h,x,y),a.addEventListener("submit",async t=>{if(t.preventDefault(),$)return;let o=[...a.querySelectorAll("input[name=scope]:checked")].map(e=>e.value),r=a.querySelector("input[name=expiry]:checked")?.value,s=c.value.trim()?o.length?null:k.querySelector("input"):c;if(x.textContent=c.value.trim()?o.length?"":"Choose read, write, or both.":"Name your token.",s)s.focus();else{$=!0,v.disabled=b.disabled=!0,v.setAttribute("aria-busy","true");try{let e=await i.request("account/tokens","POST",{name:c.value.trim(),scopes:o,expiresInDays:r&&"null"!==r?Number(r):null,projects:n.policy?.explicitProjects?[a.querySelector("input[name=projects]:checked")?.value].filter(e=>!!e):"this"===a.querySelector("input[name=projects]:checked")?.value?[p]:[]});if(A!==n)return;n.tokens.unshift(e.item),T=!1,E=e,B(),N("Token created. Copy it now. You won’t see it again."),K("api-tokens-copy")}catch(t){x.textContent=t instanceof e&&429===t.status?"You’ve created several tokens recently. Try again later.":R(t),v.focus()}finally{$=!1,v.disabled=b.disabled=!1,v.removeAttribute("aria-busy")}}}),a}(A)):E&&d.push(function(e){let a=t("section","api-tokens-sheet");a.setAttribute("aria-labelledby",`${g.id}-secret`);let i=t("h3","",`Copy “${e.item.name}”`);i.id=`${g.id}-secret`;let r=t("p","api-tokens-warning");r.append(n("info"),t("span","","You won’t see this token again. Store it in a secret manager, never in source control."));let s=t("div","api-tokens-secret"),p=t("code","",e.token);p.setAttribute("aria-label","New API token"),p.tabIndex=0,p.dataset.focusKey="api-tokens-secret";let d=o("Copy token",async()=>{try{await navigator.clipboard.writeText(e.token),d.replaceChildren(n("check"),document.createTextNode("Copied")),N("Token copied to the clipboard.")}catch{getSelection()?.selectAllChildren(p),N("Couldn’t copy. The token is selected; copy it with your keyboard.")}},"primary");d.replaceChildren(n("copy"),document.createTextNode("Copy")),d.removeAttribute("title"),d.dataset.focusKey="api-tokens-copy",s.append(p,d);let l=t("div","api-tokens-actions"),c=o("Done",()=>{E=null,B(),K("api-tokens-new")},"secondary");return c.dataset.focusKey="api-tokens-done",l.append(c),a.append(i,r,s,l),a}(E));if(a!==Y&&d[0]&&I(d[0]),Y=a,b.replaceChildren(...d),v.setAttribute("aria-busy",String(!A&&!C)),!A){if(C){let n=t("p","api-tokens-error",R(C));n.setAttribute("role","alert");let a=C instanceof e&&[403,404].includes(C.status),i=o("Try again",()=>{U()},"secondary");i.dataset.focusKey="api-tokens-retry",v.replaceChildren(n,...a?[]:[i])}else{let e=t("div","api-tokens-list");e.setAttribute("aria-hidden","true"),e.append(t("div","api-tokens-skeleton"),t("div","api-tokens-skeleton"));let n=t("span","sr-only","Loading API tokens");n.setAttribute("role","status"),v.replaceChildren(e,n)}return}if(!A.tokens.length){let e=t("p","api-tokens-empty");return e.append(t("strong","","No tokens yet"),t("span","","Create one for a bot, a script, or a CI job.")),void v.replaceChildren(e)}let l=t("ul","api-tokens-list");l.setAttribute("aria-label","Your API tokens"),l.append(...A.tokens.map(F));let c=t("p","api-tokens-note");c.append(document.createTextNode("Use a token as "),t("code","","PALETTE_TOKEN"),document.createTextNode(" with the palette CLI.")),v.replaceChildren(l,c)}async function U(){C=null,A=null,B();try{let t=await i.request("account/tokens");if(!Array.isArray(t.tokens)||!Array.isArray(t.projects))throw new e(0,"Couldn’t read your tokens. Try again.");A={...t,scopes:Array.isArray(t.scopes)?t.scopes:["comments:read","comments:write"]}}catch(e){C=e instanceof Error?e:new Error(String(e))}B()}return U(),k}export{u as accountTokens,d as relativeTime};