mockups/pr_files.html
Rama main · réplica de lectura del repositorio original.
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Files changed · Add OAuth scopes to access tokens · PR #341 · mkappworks/cloudzilla — Cloudzilla</title>
<link rel="stylesheet" href="/assets/tailwind.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500&family=Geist:wght@400;500;600&display=swap" rel="stylesheet" />
</head>
<body class="min-h-screen flex flex-col antialiased font-sans">
<a href="#main-content" class="skip-link">Skip to main content</a>
<div id="cz-header" data-repo-tab="pull_requests"></div>
<main id="main-content" tabindex="-1" class="max-w-[1400px] mx-auto px-6 pt-6 pb-20 flex-1 w-full">
<div>
<section class="mb-3 border-b border-border pb-3" aria-labelledby="pull-title">
<p class="font-mono text-[11px] text-muted-foreground uppercase tracking-wider mb-2">
<a href="repo.html" class="hover:underline">cloudzilla</a>
<span aria-hidden="true">·</span>
<a href="pulls.html" class="hover:underline">Pull requests</a>
<span aria-hidden="true">·</span>
<span aria-current="page">#341</span>
</p>
<div class="flex flex-wrap items-start justify-between gap-3 mb-3">
<h1 id="pull-title" class="text-2xl tracking-tight font-medium min-w-0">
<span id="pr-title-text">Add OAuth scopes to access tokens</span>
<span class="text-muted-foreground font-normal" aria-label="Pull request number 341">#341</span>
</h1>
<div class="flex items-center gap-1.5 shrink-0">
<button type="button" id="edit-pr-btn" class="h-8 px-3 text-[13px] border border-border rounded-md hover:bg-accent">Edit</button>
<div class="relative inline-block" data-dropdown>
<button type="button" class="h-8 px-3 text-[13px] border border-border rounded-md hover:bg-accent inline-flex items-center gap-1.5" aria-haspopup="menu" aria-expanded="false">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true" focusable="false"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg>
Code
<svg class="text-muted-foreground" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true" focusable="false"><path d="M7 10l5 5 5-5"></path></svg>
</button>
<div role="menu" class="hidden absolute right-0 top-full mt-1.5 z-50 min-w-[12rem] rounded-md border border-border bg-popover text-popover-foreground shadow-md p-1 text-sm">
<div class="px-2 py-1.5 text-[11px] uppercase tracking-wider text-muted-foreground">Check out — daisy/oauth-scopes</div>
<pre class="font-mono text-[11px] text-muted-foreground m-1 p-2 rounded-sm border border-border bg-muted overflow-x-auto whitespace-pre" onclick="event.stopPropagation()">git fetch origin
git checkout daisy/oauth-scopes</pre>
<div role="separator" class="my-1 h-px bg-border"></div>
<a href="tree.html" role="menuitem" class="flex items-center gap-2 px-2 py-1.5 rounded-xs hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-hidden whitespace-nowrap">Browse branch code</a>
</div>
</div>
</div>
</div>
<div class="flex flex-wrap items-center gap-3">
<span class="inline-flex items-center gap-1.5 h-[22px] px-2 rounded-full border text-[11px] font-medium bg-success/15 text-success border-success/30">Open</span>
<p class="text-[13px] text-muted-foreground">
<a href="profile.html" class="font-medium text-foreground underline underline-offset-2 decoration-muted-foreground/50 hover:decoration-foreground">@daisy</a>
wants to merge
<strong class="text-foreground font-medium">3 commits</strong>
into
<span class="font-mono px-1.5 py-0.5 bg-muted border border-border rounded-sm text-[12px]">main</span>
from
<span class="font-mono px-1.5 py-0.5 bg-muted border border-border rounded-sm text-[12px]">daisy/oauth-scopes</span>
</p>
</div>
</section>
<nav class="border-b border-border mb-6 py-1.5 flex flex-wrap items-center gap-1 text-sm" aria-label="Pull request views">
<a href="pr.html" class="px-3 py-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent inline-flex items-center gap-2 whitespace-nowrap">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true" focusable="false"><path d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z"></path></svg>
Conversation
<span class="font-mono text-[11px] text-muted-foreground">6</span>
</a>
<a href="pr_commits.html" class="px-3 py-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent inline-flex items-center gap-2 whitespace-nowrap">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true" focusable="false"><circle cx="12" cy="6" r="3"></circle><circle cx="12" cy="18" r="3"></circle><path d="M12 9v6"></path></svg>
Commits
<span class="font-mono text-[11px] text-muted-foreground">3</span>
</a>
<a href="pr_checks.html" class="px-3 py-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent inline-flex items-center gap-2 whitespace-nowrap">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true" focusable="false"><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="6" r="3"></circle><circle cx="18" cy="18" r="3"></circle><path d="M9 12h6M15 6L9 10M15 18l-6-4"></path></svg>
Checks
<span class="inline-flex items-center gap-1 h-[18px] px-1.5 rounded-full text-[10px] font-medium bg-success/15 text-success">✓ 6</span>
</a>
<a href="pr_files.html" class="px-3 py-1.5 rounded-md bg-accent text-foreground font-medium inline-flex items-center gap-2 whitespace-nowrap" aria-current="page">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true" focusable="false"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
Files changed
<span class="font-mono text-[11px] text-muted-foreground">4</span>
<span class="font-mono text-[10px] font-medium text-success">+41</span>
<span class="font-mono text-[10px] font-medium text-destructive">−5</span>
</a>
</nav>
<section class="mb-4 flex items-center justify-between gap-3 flex-wrap" aria-label="Diff summary">
<div class="flex items-center gap-2 text-[12.5px] text-muted-foreground">
<span><strong class="text-foreground">4</strong> changed files</span>
<span aria-hidden="true" class="opacity-50">·</span>
<span class="font-mono text-success">+41</span>
<span class="font-mono text-destructive">−5</span>
</div>
<div class="flex items-center gap-1.5">
<div class="inline-flex items-center border border-border rounded-md overflow-hidden" role="group" aria-label="Diff layout">
<button type="button" id="diff-view-unified" data-view="unified" aria-pressed="true" class="h-8 px-3 text-[12.5px] inline-flex items-center gap-1.5 hover:bg-accent bg-muted">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
Unified
</button>
<button type="button" id="diff-view-split" data-view="split" aria-pressed="false" class="h-8 px-3 text-[12.5px] inline-flex items-center gap-1.5 hover:bg-accent text-muted-foreground border-l border-border">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><rect x="3" y="3" width="8" height="18" rx="1"></rect><rect x="13" y="3" width="8" height="18" rx="1"></rect></svg>
Split
</button>
</div>
<button type="button" id="diff-ws-toggle" aria-pressed="false" class="h-8 px-3 text-[12.5px] rounded-md border border-border hover:bg-accent text-muted-foreground inline-flex items-center gap-1.5">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><path d="M17.94 17.94A10.07 10.07 0 0112 20C5 20 1 12 1 12a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>
Hide whitespace
</button>
<button type="button" id="diff-single-file" data-state="off" aria-pressed="false" class="h-8 px-3 rounded-md border border-border hover:bg-accent text-muted-foreground text-[12.5px] inline-flex items-center gap-1.5">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
Single file
</button>
<button type="button" id="commit-suggestions" data-when="batched" data-needs-write hidden class="inline-flex items-center justify-center gap-1.5 rounded-md font-medium transition-colors border border-input bg-transparent hover:bg-accent hover:text-accent-foreground h-8 px-3 text-[13px]">
Commit suggestions
<span class="font-mono text-[11px] text-muted-foreground">1</span>
</button>
<button type="button" data-needs-write onclick="document.getElementById('review-dialog').showModal()" class="inline-flex items-center justify-center gap-1.5 rounded-md font-medium transition-colors bg-success text-success-foreground hover:bg-success/90 h-8 px-3 text-[13px]">
Review changes
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M7 10l5 5 5-5"></path></svg>
</button>
</div>
</section>
<dialog id="review-dialog" aria-labelledby="review-dialog-title" class="rounded-lg border border-border bg-popover text-popover-foreground p-0 w-[480px] max-w-[calc(100vw-2rem)] max-h-[calc(100dvh-2rem)] overflow-y-auto mt-[28vh] mb-auto translate-y-[min(0px,calc(100dvh-28vh-1rem-100%))] backdrop:bg-black/60 backdrop:backdrop-blur-xs">
<div class="sticky top-0 z-10 bg-popover flex flex-col gap-1 p-5 border-b border-border">
<h2 id="review-dialog-title" class="text-base font-semibold leading-tight">Review changes</h2>
<p class="text-[13px] text-muted-foreground leading-snug">Line comments you add on the Files tab are posted as you write them. This adds your summary and verdict to <strong class="text-foreground">#341</strong>.</p>
</div>
<form id="review-form" onsubmit="document.getElementById('review-dialog').close(); czToast('Review submitted'); return false">
<div class="p-5 text-sm">
<label for="review-body" class="sr-only">Review summary</label>
<div class="rounded-md border border-border overflow-hidden">
<textarea id="review-body" name="body" rows="4" required placeholder="Leave a summary — Markdown supported" class="w-full px-3 py-3 text-[13px] font-mono leading-relaxed bg-transparent resize-none focus:outline-hidden placeholder:text-muted-foreground"></textarea>
</div>
<div role="radiogroup" aria-label="Review type" class="grid gap-3 mt-4">
<label class="flex items-start gap-3 cursor-pointer">
<input type="radio" name="state" value="commented" checked class="h-4 w-4 shrink-0 border border-input bg-transparent accent-ring checked:border-ring disabled:cursor-not-allowed disabled:opacity-50 mt-0.5" />
<span class="text-sm">
<span class="font-medium block">Comment</span>
<span class="text-muted-foreground text-[12px]">General feedback, without approving or blocking.</span>
</span>
</label>
<label class="flex items-start gap-3 cursor-pointer">
<input type="radio" name="state" value="approved" class="h-4 w-4 shrink-0 border border-input bg-transparent accent-ring checked:border-ring disabled:cursor-not-allowed disabled:opacity-50 mt-0.5" />
<span class="text-sm">
<span class="font-medium block">Approve</span>
<span class="text-muted-foreground text-[12px]">Counts toward the required approvals for main.</span>
</span>
</label>
<label class="flex items-start gap-3 cursor-pointer">
<input type="radio" name="state" value="changes_requested" class="h-4 w-4 shrink-0 border border-input bg-transparent accent-ring checked:border-ring disabled:cursor-not-allowed disabled:opacity-50 mt-0.5" />
<span class="text-sm">
<span class="font-medium block">Request changes</span>
<span class="text-muted-foreground text-[12px]">Blocks merging until you approve or your review is dismissed.</span>
</span>
</label>
</div>
</div>
</form>
<div class="sticky bottom-0 z-10 bg-popover flex items-center justify-end gap-2 p-5 border-t border-border">
<form method="dialog" class="contents">
<button type="submit" class="inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors border border-input bg-transparent hover:bg-accent hover:text-accent-foreground h-9 px-4">Cancel</button>
</form>
<button type="submit" form="review-form" class="inline-flex items-center justify-center gap-1.5 rounded-md font-medium transition-colors bg-success text-success-foreground hover:bg-success/90 h-9 px-4 text-sm">Submit review</button>
</div>
</dialog>
<div id="diff-root" data-view="unified" class="grid lg:grid-cols-[260px_1fr] gap-4">
<aside class="border border-border rounded-md bg-card p-3 lg:sticky lg:top-4 self-start max-h-[80vh] overflow-auto" aria-label="Files in this pull request">
<div class="flex items-center justify-between gap-2 mb-2 px-1">
<p class="font-mono text-[10px] text-muted-foreground uppercase tracking-wider">Changed files</p>
<div class="inline-flex items-center border border-border rounded-sm overflow-hidden" role="group" aria-label="File list layout">
<button type="button" id="diff-files-list-btn" aria-pressed="true" title="List view" aria-label="List view" class="px-1.5 py-1 inline-flex hover:bg-accent bg-muted">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>
</button>
<button type="button" id="diff-files-tree-btn" aria-pressed="false" title="Tree view" aria-label="Tree view" class="px-1.5 py-1 inline-flex hover:bg-accent text-muted-foreground border-l border-border">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><path d="M21 12h-8"></path><path d="M21 6H8"></path><path d="M21 18h-8"></path><path d="M3 6v4c0 1.1.9 2 2 2h3"></path><path d="M3 10v6c0 1.1.9 2 2 2h3"></path></svg>
</button>
</div>
</div>
<ul id="diff-files-list" class="text-[12.5px] space-y-0.5">
<li>
<a href="#diff-0" class="flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" class="shrink-0 opacity-70" aria-hidden="true"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
<span class="truncate flex-1 font-mono">docs/api-reference.md</span>
<span class="font-mono text-[10px] text-success">+9</span>
</a>
</li>
<li>
<a href="#diff-1" class="flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" class="shrink-0 opacity-70" aria-hidden="true"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
<span class="truncate flex-1 font-mono">internal/db/migrations/0042_access_token_scopes.sql</span>
<span class="font-mono text-[10px] text-success">+12</span>
</a>
</li>
<li>
<a href="#diff-2" class="flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" class="shrink-0 opacity-70" aria-hidden="true"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
<span class="truncate flex-1 font-mono">internal/middleware/auth.go</span>
<span class="font-mono text-[10px] text-success">+14</span><span class="font-mono text-[10px] text-destructive">−5</span>
</a>
</li>
<li>
<a href="#diff-3" class="flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" class="shrink-0 opacity-70" aria-hidden="true"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
<span class="truncate flex-1 font-mono">internal/store/token_store.go</span>
<span class="font-mono text-[10px] text-success">+6</span>
</a>
</li>
</ul>
<ul id="diff-files-tree" class="text-[12.5px] space-y-0.5 hidden">
<li data-open="true">
<button type="button" class="diff-tree-folder w-full flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground" style="padding-left: 8px">
<svg class="diff-tree-caret shrink-0 transition-transform" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>
<span class="truncate font-mono">docs</span>
</button>
<ul>
<li>
<a href="#diff-0" class="flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground" style="padding-left: 20px">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" class="shrink-0 opacity-70" aria-hidden="true"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
<span class="truncate flex-1 font-mono">api-reference.md</span>
<span class="font-mono text-[10px] text-success">+9</span>
</a>
</li>
</ul>
</li>
<li data-open="true">
<button type="button" class="diff-tree-folder w-full flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground" style="padding-left: 8px">
<svg class="diff-tree-caret shrink-0 transition-transform" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>
<span class="truncate font-mono">internal</span>
</button>
<ul>
<li data-open="true">
<button type="button" class="diff-tree-folder w-full flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground" style="padding-left: 20px">
<svg class="diff-tree-caret shrink-0 transition-transform" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>
<span class="truncate font-mono">db</span>
</button>
<ul>
<li data-open="true">
<button type="button" class="diff-tree-folder w-full flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground" style="padding-left: 32px">
<svg class="diff-tree-caret shrink-0 transition-transform" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>
<span class="truncate font-mono">migrations</span>
</button>
<ul>
<li>
<a href="#diff-1" class="flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground" style="padding-left: 44px">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" class="shrink-0 opacity-70" aria-hidden="true"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
<span class="truncate flex-1 font-mono">0042_access_token_scopes.sql</span>
<span class="font-mono text-[10px] text-success">+12</span>
</a>
</li>
</ul>
</li>
</ul>
</li>
<li data-open="true">
<button type="button" class="diff-tree-folder w-full flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground" style="padding-left: 20px">
<svg class="diff-tree-caret shrink-0 transition-transform" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>
<span class="truncate font-mono">middleware</span>
</button>
<ul>
<li>
<a href="#diff-2" class="flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground" style="padding-left: 32px">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" class="shrink-0 opacity-70" aria-hidden="true"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
<span class="truncate flex-1 font-mono">auth.go</span>
<span class="font-mono text-[10px] text-success">+14</span><span class="font-mono text-[10px] text-destructive">−5</span>
</a>
</li>
</ul>
</li>
<li data-open="true">
<button type="button" class="diff-tree-folder w-full flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground" style="padding-left: 20px">
<svg class="diff-tree-caret shrink-0 transition-transform" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>
<span class="truncate font-mono">store</span>
</button>
<ul>
<li>
<a href="#diff-3" class="flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground" style="padding-left: 32px">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" class="shrink-0 opacity-70" aria-hidden="true"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
<span class="truncate flex-1 font-mono">token_store.go</span>
<span class="font-mono text-[10px] text-success">+6</span>
</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</aside>
<div class="space-y-6 min-w-0">
<section id="diff-0" class="diff-file border border-border rounded-md overflow-hidden scroll-mt-6">
<header class="diff-file-header px-3 py-2 border-b border-border bg-muted/40 flex items-center gap-2 text-[12.5px] cursor-pointer select-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring" role="button" tabindex="0" aria-expanded="true">
<svg class="diff-file-caret shrink-0 text-muted-foreground transition-transform" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>
<span class="font-mono font-medium truncate">docs/api-reference.md</span>
<span class="ml-auto font-mono text-[11px] shrink-0">
<span class="text-success">+9</span>
</span>
<a href="blob.html" class="text-[12px] text-muted-foreground hover:text-foreground shrink-0">View file →</a>
</header>
<div class="diff-file-body">
<div class="diff-unified">
<div class="hl overflow-x-auto">
<table class="w-full border-collapse text-xs font-mono">
<tbody>
<tr><td colspan="2" class="bg-primary/5 text-muted-foreground px-4 py-1 select-none border-y border-border">@@ -118,6 +118,14 @@ ## Access tokens</td></tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">118<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> Personal access tokens authenticate API requests with a bearer header.</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">119<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> </td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">120<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+### Scopes</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">121<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">122<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td id="sugg-target" class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+A token carries a set of scopes. A request outside the token's scopes is rejected with `403`.</td>
</tr>
<tr data-thread class="bg-primary/10 border-t border-primary/20">
<td colspan="2" class="px-4 py-0">
<div class="flex items-start gap-2 py-2 border-b border-primary/20 last:border-0">
<span class="flex shrink-0 mt-0.5"><span class="inline-grid place-items-center rounded-full bg-muted border border-border font-medium text-muted-foreground h-6 w-6 text-[10px]" aria-hidden="true">Y</span></span>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2">
<a href="profile.html" class="text-xs font-semibold hover:underline">yumi</a>
<span class="text-xs text-muted-foreground">Nov 6, 2026</span>
</div>
<div class="text-xs text-foreground mt-0.5 prose prose-xs dark:prose-invert max-w-none"><p>Token already has a helper for this, and it handles the wildcard.</p></div>
<div class="mt-2 mb-1 rounded-md border border-border bg-card overflow-hidden font-sans">
<div class="px-3 py-1.5 border-b border-border bg-muted/40 flex items-center gap-2 text-[11px] text-muted-foreground">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 113 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
Suggested change
<span data-when="applied" hidden class="inline-flex items-center gap-1.5 h-[22px] px-2 rounded-full border text-[11px] font-medium bg-success/15 text-success border-success/30">Applied</span>
<span data-when="batched" hidden class="inline-flex items-center gap-1.5 h-[22px] px-2 rounded-full border text-[11px] font-medium bg-secondary text-secondary-foreground border-transparent">In batch</span>
<span class="ml-auto font-mono">line 122</span>
</div>
<table class="w-full border-collapse text-xs font-mono">
<tbody>
<tr class="bg-destructive/10"><td class="w-10 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5">122</td><td class="pl-3 pr-4 py-0.5 whitespace-pre text-destructive">-A token carries a set of scopes. A request outside the token's scopes is rejected with `403`.</td></tr>
<tr class="bg-success/10"><td class="w-10 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5">122</td><td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+A token carries a set of scopes (`*` grants all). A request outside them is rejected with `403`.</td></tr>
</tbody>
</table>
<div data-when="applied" hidden class="px-3 py-2 border-t border-border">
<p class="text-[12px] text-muted-foreground">
<span class="text-foreground">daisy</span> applied this in
<a href="commit.html" class="font-mono text-foreground hover:underline">4e1c0d2</a>
· Nov 7, 2026
</p>
</div>
<div data-when="pending" data-needs-write class="px-3 py-2 border-t border-border flex flex-wrap items-center gap-2">
<button type="button" data-set="applied" class="inline-flex items-center justify-center gap-1.5 rounded-md font-medium transition-colors bg-success text-success-foreground hover:bg-success/90 h-8 px-3 text-[13px]">Apply suggestion</button>
<button type="button" data-set="batched" class="inline-flex items-center justify-center gap-1.5 rounded-md font-medium transition-colors border border-input bg-transparent hover:bg-accent hover:text-accent-foreground h-8 px-3 text-[13px]">Add to batch</button>
</div>
<div data-when="batched" data-needs-write hidden class="px-3 py-2 border-t border-border flex flex-wrap items-center gap-2">
<button type="button" data-set="pending" class="inline-flex items-center justify-center gap-1.5 rounded-md font-medium transition-colors border border-input bg-transparent hover:bg-accent hover:text-accent-foreground h-8 px-3 text-[13px]">Remove from batch</button>
<p class="text-[12px] text-muted-foreground">Committed together with the other batched suggestions.</p>
</div>
</div>
</div>
<button type="button" class="text-muted-foreground/60 hover:text-destructive text-xs shrink-0" aria-label="Delete comment">×</button>
</div>
</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">123<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">124<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+| Scope | Grants |</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">125<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+| ------------ | ------------------------------- |</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">126<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+| `repo:read` | Read repositories and metadata |</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">127<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+| `repo:write` | Push, open and merge pull requests |</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">128<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">129<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> Tokens created before scopes existed are migrated to `scopes=*`.</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">130<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> </td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">131<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> ## Errors</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="diff-split">
<div class="hl overflow-x-auto">
<table class="w-full table-fixed border-collapse text-xs font-mono">
<colgroup><col class="w-10"/><col/><col class="w-10"/><col/></colgroup>
<tbody>
<tr><td colspan="4" class="bg-primary/5 text-muted-foreground px-4 py-1 select-none border-y border-border">@@ -118,6 +118,14 @@ ## Access tokens</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">118</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> Personal access tokens authenticate API requests with a bearer header.</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">118</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> Personal access tokens authenticate API requests with a bearer header.</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">119</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> </td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">119</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> </td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">120</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+### Scopes</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">121</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">122</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+A token carries a set of scopes. A request outside the token's scopes is rejected with `403`.</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">123</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">124</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+| Scope | Grants |</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">125</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+| ------------ | ------------------------------- |</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">126</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+| `repo:read` | Read repositories and metadata |</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">127</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+| `repo:write` | Push, open and merge pull requests |</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">128</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">120</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> Tokens created before scopes existed are migrated to `scopes=*`.</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">129</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> Tokens created before scopes existed are migrated to `scopes=*`.</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">121</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> </td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">130</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> </td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">122</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> ## Errors</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">131</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> ## Errors</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<section id="diff-1" class="diff-file border border-border rounded-md overflow-hidden scroll-mt-6">
<header class="diff-file-header px-3 py-2 border-b border-border bg-muted/40 flex items-center gap-2 text-[12.5px] cursor-pointer select-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring" role="button" tabindex="0" aria-expanded="true">
<svg class="diff-file-caret shrink-0 text-muted-foreground transition-transform" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>
<span class="font-mono font-medium truncate">internal/db/migrations/0042_access_token_scopes.sql</span>
<span class="ml-auto font-mono text-[11px] shrink-0">
<span class="text-success">+12</span>
</span>
<a href="blob.html" class="text-[12px] text-muted-foreground hover:text-foreground shrink-0">View file →</a>
</header>
<div class="diff-file-body">
<div class="diff-unified">
<div class="hl overflow-x-auto">
<table class="w-full border-collapse text-xs font-mono">
<tbody>
<tr><td colspan="2" class="bg-primary/5 text-muted-foreground px-4 py-1 select-none border-y border-border">@@ -0,0 +1,12 @@</td></tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">1<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+CREATE TABLE access_token_scopes (</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">2<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ id BIGSERIAL PRIMARY KEY,</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">3<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ access_token_id BIGINT NOT NULL REFERENCES access_tokens (id) ON DELETE CASCADE,</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">4<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ scope TEXT NOT NULL,</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">5<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ UNIQUE (access_token_id, scope)</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">6<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+);</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">7<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">8<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+INSERT INTO access_token_scopes (access_token_id, scope)</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">9<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+SELECT id, '*' FROM access_tokens;</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">10<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">11<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+CREATE INDEX idx_access_token_scopes_token ON access_token_scopes (access_token_id);</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">12<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="diff-split">
<div class="hl overflow-x-auto">
<table class="w-full border-collapse text-xs font-mono">
<tbody>
<tr><td colspan="2" class="bg-primary/5 text-muted-foreground px-4 py-1 select-none border-y border-border">@@ -0,0 +1,12 @@</td></tr>
<tr class="bg-success/6 dark:bg-success/10"><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">1</td><td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+CREATE TABLE access_token_scopes (</td></tr>
<tr class="bg-success/6 dark:bg-success/10"><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">2</td><td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ id BIGSERIAL PRIMARY KEY,</td></tr>
<tr class="bg-success/6 dark:bg-success/10"><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">3</td><td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ access_token_id BIGINT NOT NULL REFERENCES access_tokens (id) ON DELETE CASCADE,</td></tr>
<tr class="bg-success/6 dark:bg-success/10"><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">4</td><td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ scope TEXT NOT NULL,</td></tr>
<tr class="bg-success/6 dark:bg-success/10"><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">5</td><td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ UNIQUE (access_token_id, scope)</td></tr>
<tr class="bg-success/6 dark:bg-success/10"><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">6</td><td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+);</td></tr>
<tr class="bg-success/6 dark:bg-success/10"><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">7</td><td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+</td></tr>
<tr class="bg-success/6 dark:bg-success/10"><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">8</td><td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+INSERT INTO access_token_scopes (access_token_id, scope)</td></tr>
<tr class="bg-success/6 dark:bg-success/10"><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">9</td><td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+SELECT id, '*' FROM access_tokens;</td></tr>
<tr class="bg-success/6 dark:bg-success/10"><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">10</td><td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+</td></tr>
<tr class="bg-success/6 dark:bg-success/10"><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">11</td><td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+CREATE INDEX idx_access_token_scopes_token ON access_token_scopes (access_token_id);</td></tr>
<tr class="bg-success/6 dark:bg-success/10"><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">12</td><td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<section id="diff-2" class="diff-file border border-border rounded-md overflow-hidden scroll-mt-6">
<header class="diff-file-header px-3 py-2 border-b border-border bg-muted/40 flex items-center gap-2 text-[12.5px] cursor-pointer select-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring" role="button" tabindex="0" aria-expanded="true">
<svg class="diff-file-caret shrink-0 text-muted-foreground transition-transform" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>
<span class="font-mono font-medium truncate">internal/middleware/auth.go</span>
<span class="ml-auto font-mono text-[11px] shrink-0">
<span class="text-success">+14</span>
<span class="text-destructive ml-1">−5</span>
</span>
<a href="blob.html" class="text-[12px] text-muted-foreground hover:text-foreground shrink-0">View file →</a>
</header>
<div class="diff-file-body">
<div class="diff-unified">
<div class="hl overflow-x-auto">
<table class="w-full border-collapse text-xs font-mono">
<tbody>
<tr><td colspan="2" class="bg-primary/5 text-muted-foreground px-4 py-1 select-none border-y border-border">@@ -68,12 +68,24 @@ func RequireAuth(next http.Handler) http.Handler {</td></tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">68<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> return</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">69<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> }</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">70<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> </td>
</tr>
<tr class="diff-line ws-only bg-destructive/6 dark:bg-destructive/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">71</td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-destructive">- next.ServeHTTP(w, r)</td>
</tr>
<tr class="diff-line ws-only bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">71<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ next.ServeHTTP(w, r)</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">72<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> })</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">73<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> }</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">74<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> </td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">75<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+// RequireScope rejects requests whose token lacks the scope.</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">76<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+func RequireScope(scope string) func(http.Handler) http.Handler {</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">77<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ return func(next http.Handler) http.Handler {</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">78<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">79<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ token := TokenFromContext(r.Context())</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">80<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ if !slices.Contains(token.Scopes, scope) && !slices.Contains(token.Scopes, "*") {</td>
</tr>
<tr data-thread class="bg-primary/10 border-t border-primary/20">
<td colspan="2" class="px-4 py-0">
<div class="flex items-start gap-2 py-2 border-b border-primary/20 last:border-0">
<span class="flex shrink-0 mt-0.5"><span class="inline-grid place-items-center rounded-full bg-muted border border-border font-medium text-muted-foreground h-6 w-6 text-[10px]" aria-hidden="true">Y</span></span>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2">
<a href="profile.html" class="text-xs font-semibold hover:underline">yumi</a>
<span class="text-xs text-muted-foreground">May 13, 2026</span>
</div>
<div class="text-xs text-foreground mt-0.5 prose prose-xs dark:prose-invert max-w-none"><p>Unscoped legacy tokens now get <code>*</code> from the migration, so this branch is safe. Thanks for dropping <code>legacyAllScopes</code>.</p></div>
</div>
<button type="button" class="text-muted-foreground/60 hover:text-destructive text-xs shrink-0" aria-label="Delete comment">×</button>
</div>
</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">81<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ http.Error(w, "missing scope", http.StatusForbidden)</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">82<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ return</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">83<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ }</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">84<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ next.ServeHTTP(w, r)</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">85<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ })</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">86<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ }</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">87<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+}</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">88<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> </td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">89<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> func TokenFromContext(ctx context.Context) *model.AccessToken {</td>
</tr>
<tr><td colspan="2" class="bg-primary/5 text-muted-foreground px-4 py-1 select-none border-y border-border">@@ -88,7 +100,3 @@ func TokenFromContext(ctx context.Context) *model.AccessToken {</td></tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">100<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> return t</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">101<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> }</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">102<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> </td>
</tr>
<tr class="diff-line bg-destructive/6 dark:bg-destructive/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">91</td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-destructive">-// legacyAllScopes treated a token with no scopes as unrestricted.</td>
</tr>
<tr class="diff-line bg-destructive/6 dark:bg-destructive/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">92</td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-destructive">-func legacyAllScopes(t *model.AccessToken) bool {</td>
</tr>
<tr class="diff-line bg-destructive/6 dark:bg-destructive/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">93</td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-destructive">- return len(t.Scopes) == 0</td>
</tr>
<tr class="diff-line bg-destructive/6 dark:bg-destructive/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">94</td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-destructive">-}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="diff-split">
<div class="hl overflow-x-auto">
<table class="w-full table-fixed border-collapse text-xs font-mono">
<colgroup><col class="w-10"/><col/><col class="w-10"/><col/></colgroup>
<tbody>
<tr><td colspan="4" class="bg-primary/5 text-muted-foreground px-4 py-1 select-none border-y border-border">@@ -68,12 +68,24 @@ func RequireAuth(next http.Handler) http.Handler {</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">68</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> return</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">68</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> return</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">69</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> }</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">69</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> }</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">70</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> </td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">70</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> </td></tr>
<tr class="ws-only"><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">71</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden bg-destructive/6 dark:bg-destructive/10 text-destructive">- next.ServeHTTP(w, r)</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">71</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ next.ServeHTTP(w, r)</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">72</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> })</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">72</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> })</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">73</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> }</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">73</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> }</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">74</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> </td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">74</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> </td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">75</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+// RequireScope rejects requests whose token lacks the scope.</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">76</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+func RequireScope(scope string) func(http.Handler) http.Handler {</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">77</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ return func(next http.Handler) http.Handler {</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">78</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">79</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ token := TokenFromContext(r.Context())</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">80</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ if !slices.Contains(token.Scopes, scope) && !slices.Contains(token.Scopes, "*") {</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">81</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ http.Error(w, "missing scope", http.StatusForbidden)</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">82</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ return</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">83</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ }</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">84</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ next.ServeHTTP(w, r)</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">85</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ })</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">86</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ }</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">87</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+}</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">75</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> </td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">88</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> </td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">76</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> func TokenFromContext(ctx context.Context) *model.AccessToken {</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">89</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> func TokenFromContext(ctx context.Context) *model.AccessToken {</td></tr>
<tr><td colspan="4" class="bg-primary/5 text-muted-foreground px-4 py-1 select-none border-y border-border">@@ -88,7 +100,3 @@ func TokenFromContext(ctx context.Context) *model.AccessToken {</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">88</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> return t</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">100</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> return t</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">89</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> }</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">101</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> }</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">90</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> </td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">102</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> </td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">91</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden bg-destructive/6 dark:bg-destructive/10 text-destructive">-// legacyAllScopes treated a token with no scopes as unrestricted.</td><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30 border-l border-border"></td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">92</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden bg-destructive/6 dark:bg-destructive/10 text-destructive">-func legacyAllScopes(t *model.AccessToken) bool {</td><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30 border-l border-border"></td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">93</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden bg-destructive/6 dark:bg-destructive/10 text-destructive">- return len(t.Scopes) == 0</td><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30 border-l border-border"></td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">94</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden bg-destructive/6 dark:bg-destructive/10 text-destructive">-}</td><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30 border-l border-border"></td></tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<section id="diff-3" class="diff-file border border-border rounded-md overflow-hidden scroll-mt-6">
<header class="diff-file-header px-3 py-2 border-b border-border bg-muted/40 flex items-center gap-2 text-[12.5px] cursor-pointer select-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring" role="button" tabindex="0" aria-expanded="true">
<svg class="diff-file-caret shrink-0 text-muted-foreground transition-transform" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>
<span class="font-mono font-medium truncate">internal/store/token_store.go</span>
<span class="ml-auto font-mono text-[11px] shrink-0">
<span class="text-success">+6</span>
</span>
<a href="blob.html" class="text-[12px] text-muted-foreground hover:text-foreground shrink-0">View file →</a>
</header>
<div class="diff-file-body">
<div class="diff-unified">
<div class="hl overflow-x-auto">
<table class="w-full border-collapse text-xs font-mono">
<tbody>
<tr><td colspan="2" class="bg-primary/5 text-muted-foreground px-4 py-1 select-none border-y border-border">@@ -40,6 +40,13 @@ func (s *TokenStore) Create(ctx context.Context, t model.AccessToken) error {</td></tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">40<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> err := s.db.QueryRowContext(ctx, `INSERT INTO access_tokens (user_id, name, hash)</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">41<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> VALUES ($1, $2, $3) RETURNING id`, t.UserID, t.Name, t.Hash).Scan(&t.ID)</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">42<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> if err != nil {</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">43<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> return err</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">44<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> }</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">45<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ for _, scope := range t.Scopes {</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">46<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ if _, err := s.db.ExecContext(ctx, `INSERT INTO access_token_scopes (access_token_id, scope)</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">47<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ VALUES ($1, $2)`, t.ID, scope); err != nil {</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">48<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ return err</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">49<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ }</td>
</tr>
<tr class="diff-line bg-success/6 dark:bg-success/10">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">50<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-success">+ }</td>
</tr>
<tr class="diff-line">
<td class="group w-14 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">51<button type="button" data-comment class="ml-1 text-muted-foreground/40 group-hover:text-primary" aria-label="Comment on this line">+</button></td>
<td class="pl-3 pr-4 py-0.5 whitespace-pre text-foreground/85"> return nil</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="diff-split">
<div class="hl overflow-x-auto">
<table class="w-full table-fixed border-collapse text-xs font-mono">
<colgroup><col class="w-10"/><col/><col class="w-10"/><col/></colgroup>
<tbody>
<tr><td colspan="4" class="bg-primary/5 text-muted-foreground px-4 py-1 select-none border-y border-border">@@ -40,6 +40,13 @@ func (s *TokenStore) Create(ctx context.Context, t model.AccessToken) error {</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">40</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> err := s.db.QueryRowContext(ctx, `INSERT INTO access_tokens (user_id, name, hash)</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">40</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> err := s.db.QueryRowContext(ctx, `INSERT INTO access_tokens (user_id, name, hash)</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">41</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> VALUES ($1, $2, $3) RETURNING id`, t.UserID, t.Name, t.Hash).Scan(&t.ID)</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">41</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> VALUES ($1, $2, $3) RETURNING id`, t.UserID, t.Name, t.Hash).Scan(&t.ID)</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">42</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> if err != nil {</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">42</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> if err != nil {</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">43</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> return err</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">43</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> return err</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">44</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> }</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">44</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> }</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">45</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ for _, scope := range t.Scopes {</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">46</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ if _, err := s.db.ExecContext(ctx, `INSERT INTO access_token_scopes (access_token_id, scope)</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">47</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ VALUES ($1, $2)`, t.ID, scope); err != nil {</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">48</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ return err</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">49</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ }</td></tr>
<tr><td class="w-12 bg-muted/30 border-r border-border"></td><td class="px-3 py-0.5 bg-muted/30"></td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">50</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border bg-success/6 dark:bg-success/10 text-success">+ }</td></tr>
<tr><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">45</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden text-foreground/85"> return nil</td><td class="w-12 text-right pr-2 text-muted-foreground select-none border-r border-border py-0.5 align-top">51</td><td class="pl-3 pr-4 py-0.5 whitespace-pre overflow-hidden border-l border-border text-foreground/85"> return nil</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
</main>
<div id="cz-footer"></div>
<script src="shell.js"></script>
<script>
(function () {
var btn = document.getElementById('edit-pr-btn');
var txt = document.getElementById('pr-title-text');
var original = txt.textContent;
var editing = ['rounded-sm', 'bg-muted', 'px-1', 'outline-solid', 'outline-1', 'outline-border'];
btn.addEventListener('click', function () {
if (txt.isContentEditable) {
var next = txt.textContent.trim();
txt.contentEditable = 'false';
txt.classList.remove.apply(txt.classList, editing);
btn.textContent = 'Edit';
if (next) original = next; else txt.textContent = original;
} else {
txt.contentEditable = 'true';
txt.classList.add.apply(txt.classList, editing);
btn.textContent = 'Save';
txt.focus();
}
});
})();
</script>
<script>
(function () {
var root = document.getElementById('diff-root');
var sections = Array.prototype.slice.call(document.querySelectorAll('.diff-file'));
var singleBtn = document.getElementById('diff-single-file');
var activeId = sections.length ? sections[0].id : null;
var fileLinks = {};
document.querySelectorAll('#diff-files-list a[href^="#diff-"], #diff-files-tree a[href^="#diff-"]').forEach(function (a) {
var id = a.getAttribute('href').slice(1);
(fileLinks[id] = fileLinks[id] || []).push(a);
});
function setCollapsed(section, collapsed) {
section.querySelector('.diff-file-body').classList.toggle('hidden', collapsed);
section.querySelector('.diff-file-caret').style.transform = collapsed ? 'rotate(-90deg)' : '';
section.querySelector('.diff-file-header').setAttribute('aria-expanded', collapsed ? 'false' : 'true');
}
document.querySelectorAll('.diff-file-header').forEach(function (h) {
function toggle() {
var s = h.closest('.diff-file');
setCollapsed(s, !s.querySelector('.diff-file-body').classList.contains('hidden'));
}
h.addEventListener('click', function (e) {
if (e.target.closest('a')) return;
toggle();
});
h.addEventListener('keydown', function (e) {
if (e.target !== h || (e.key !== 'Enter' && e.key !== ' ')) return;
e.preventDefault();
toggle();
});
});
function singleOn() { return singleBtn.dataset.state === 'on'; }
function highlight(id) {
Object.keys(fileLinks).forEach(function (k) {
fileLinks[k].forEach(function (a) {
a.classList.toggle('bg-accent', k === id);
a.classList.toggle('text-foreground', k === id);
});
});
}
function applySingle() {
var on = singleOn();
sections.forEach(function (s) { s.classList.toggle('hidden', on && s.id !== activeId); });
if (on) window.scrollTo(0, 0);
}
function setActive(id) { activeId = id; highlight(id); applySingle(); }
Object.keys(fileLinks).forEach(function (id) {
fileLinks[id].forEach(function (a) {
a.addEventListener('click', function (e) {
if (singleOn()) e.preventDefault();
setActive(id);
});
});
});
singleBtn.addEventListener('click', function () {
var on = !singleOn();
singleBtn.dataset.state = on ? 'on' : 'off';
singleBtn.setAttribute('aria-pressed', on);
singleBtn.classList.toggle('bg-muted', on);
singleBtn.classList.toggle('text-muted-foreground', !on);
applySingle();
});
if ('IntersectionObserver' in window) {
var spy = new IntersectionObserver(function (entries) {
if (singleOn()) return;
entries.forEach(function (e) { if (e.isIntersecting) { activeId = e.target.id; highlight(activeId); } });
}, { rootMargin: '-8% 0px -82% 0px' });
sections.forEach(function (s) { spy.observe(s); });
}
highlight(activeId);
function pair(a, b, first) {
a.setAttribute('aria-pressed', first);
b.setAttribute('aria-pressed', !first);
a.classList.toggle('bg-muted', first);
a.classList.toggle('text-muted-foreground', !first);
b.classList.toggle('bg-muted', !first);
b.classList.toggle('text-muted-foreground', first);
}
var unifiedBtn = document.getElementById('diff-view-unified');
var splitBtn = document.getElementById('diff-view-split');
unifiedBtn.addEventListener('click', function () { root.dataset.view = 'unified'; pair(unifiedBtn, splitBtn, true); });
splitBtn.addEventListener('click', function () { root.dataset.view = 'split'; pair(unifiedBtn, splitBtn, false); });
var wsBtn = document.getElementById('diff-ws-toggle');
wsBtn.addEventListener('click', function () {
var on = !root.classList.contains('hide-ws');
root.classList.toggle('hide-ws', on);
wsBtn.setAttribute('aria-pressed', on);
wsBtn.classList.toggle('bg-muted', on);
wsBtn.classList.toggle('text-muted-foreground', !on);
});
var listUl = document.getElementById('diff-files-list');
var treeUl = document.getElementById('diff-files-tree');
var listBtn = document.getElementById('diff-files-list-btn');
var treeBtn = document.getElementById('diff-files-tree-btn');
listBtn.addEventListener('click', function () { listUl.classList.remove('hidden'); treeUl.classList.add('hidden'); pair(listBtn, treeBtn, true); });
treeBtn.addEventListener('click', function () { listUl.classList.add('hidden'); treeUl.classList.remove('hidden'); pair(listBtn, treeBtn, false); });
treeUl.querySelectorAll('.diff-tree-folder').forEach(function (b) {
b.addEventListener('click', function () {
var li = b.closest('li');
var open = li.dataset.open !== 'false';
li.dataset.open = open ? 'false' : 'true';
b.querySelector('.diff-tree-caret').style.transform = open ? 'rotate(-90deg)' : '';
li.querySelector(':scope > ul').classList.toggle('hidden', open);
});
});
var FORM = '<td colspan="2" class="p-0"><form class="p-2 space-y-2">' +
'<textarea name="body" rows="2" placeholder="Leave a comment..." required class="w-full border border-border rounded-sm px-2 py-1 text-xs focus:outline-hidden focus:ring-1 focus-visible:outline-ring resize-none"></textarea>' +
'<div class="flex gap-2"><button type="submit" class="px-3 py-1 bg-primary text-background rounded-sm text-xs font-medium hover:bg-primary">Comment</button>' +
'<button type="button" data-cancel class="px-3 py-1 bg-background border border-border text-muted-foreground rounded-sm text-xs hover:bg-muted/40">Cancel</button></div></form></td>';
var POSTED = '<td colspan="2" class="px-4 py-0"><div class="flex items-start gap-2 py-2"><span class="flex shrink-0 mt-0.5"><span class="inline-grid place-items-center rounded-full bg-muted border border-border font-medium text-muted-foreground h-6 w-6 text-[10px]" aria-hidden="true">M</span></span>' +
'<div class="flex-1 min-w-0"><div class="flex items-center gap-2"><a href="profile.html" class="text-xs font-semibold hover:underline">malith</a><span class="text-xs text-muted-foreground">just now</span></div>' +
'<div class="text-xs text-foreground mt-0.5" data-body></div></div></div></td>';
document.querySelectorAll('[data-comment]').forEach(function (b) {
b.addEventListener('click', function () {
var tr = b.closest('tr');
var next = tr.nextElementSibling;
if (next && next.hasAttribute('data-cform')) return;
var row = document.createElement('tr');
row.setAttribute('data-cform', '');
row.innerHTML = FORM;
tr.after(row);
var form = row.querySelector('form');
row.querySelector('textarea').focus();
row.querySelector('[data-cancel]').addEventListener('click', function () { row.remove(); });
form.addEventListener('submit', function (e) {
e.preventDefault();
var text = form.elements.body.value.trim();
if (!text) return;
row.className = 'bg-primary/10 border-t border-primary/20';
row.removeAttribute('data-cform');
row.innerHTML = POSTED;
row.querySelector('[data-body]').textContent = text;
});
});
});
})();
</script>
<script>
(function () {
var ORIGINAL = document.getElementById('sugg-target').textContent;
var UPDATED = '+A token carries a set of scopes (`*` grants all). A request outside them is rejected with `403`.';
function params() {
var h = location.hash.slice(1).split('&'), out = {};
h.forEach(function (p) { var kv = p.split('='); out[kv[0]] = kv[1]; });
return out;
}
function render() {
var p = params();
var state = ['pending', 'batched', 'applied'].indexOf(p.sugg) >= 0 ? p.sugg : 'pending';
var readonly = p.viewer === 'readonly';
document.querySelectorAll('[data-when]').forEach(function (el) {
el.hidden = el.getAttribute('data-when').split(' ').indexOf(state) < 0 || (readonly && el.hasAttribute('data-needs-write'));
});
document.querySelectorAll('[data-needs-write]:not([data-when])').forEach(function (el) { el.hidden = readonly; });
document.getElementById('sugg-target').textContent = state === 'applied' ? UPDATED : ORIGINAL;
}
function set(state) {
var p = params();
location.hash = 'sugg=' + state + (p.viewer ? '&viewer=' + p.viewer : '');
}
document.querySelectorAll('[data-set]').forEach(function (b) {
b.addEventListener('click', function () { set(b.getAttribute('data-set')); });
});
document.getElementById('commit-suggestions').addEventListener('click', function () { set('applied'); });
var body = document.getElementById('review-body');
document.querySelectorAll('#review-form input[name=state]').forEach(function (r) {
r.addEventListener('change', function () { body.required = r.value !== 'approved'; });
});
window.addEventListener('hashchange', render);
render();
})();
</script>
<script>
document.addEventListener('click', function (e) {
var b = e.target.closest('button[aria-label="Delete comment"]');
if (!b) return;
var thread = b.closest('tr[data-thread]');
b.parentElement.remove();
if (!thread.querySelector('button[aria-label="Delete comment"]')) thread.remove();
czToast('Comment deleted');
});
</script>
</body>
</html>