also looking at this
Promote main to production: signed-out chrome and the subscriber tables
#17
54 files
+4252
-1353
Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
| @@ -1095,11 +1095,71 @@ export default { | ||
| 1095 | 1095 | + 'focus:outline-none focus:border-accent ' |
| 1096 | 1096 | + 'focus:shadow-[0_0_0_3px_color-mix(in_srgb,var(--accent)_16%,transparent)]', |
| 1097 | 1097 | |
| 1098 | /* | |
| 1099 | * The union of the transition lists, not this app's first one. | |
| 1100 | * | |
| 1101 | * This entry animated transform and opacity only, so the accent fill it | |
| 1102 | * sets on :hover SNAPPED. settings.stx's two solid buttons declared | |
| 1103 | * `background 0.15s ease, opacity 0.15s ease` instead and eased it, which | |
| 1104 | * is how the omission surfaced when they were folded in here. Each copy had | |
| 1105 | * named the properties its own call site happened to animate; the union is | |
| 1106 | * what all of them meant. bughq's btn-solid took the same union for the | |
| 1107 | * same reason. | |
| 1108 | */ | |
| 1098 | 1109 | 'btn-solid': 'bg-accent text-accent-ink rounded-[10px] ' |
| 1099 | + '[transition:transform_0.12s_ease,opacity_0.15s_ease] ' | |
| 1110 | + '[transition:transform_0.12s_ease,opacity_0.15s_ease,background-color_0.15s_ease] ' | |
| 1100 | 1111 | + 'hover:bg-[color-mix(in_srgb,var(--accent)_88%,#000)] ' |
| 1101 | 1112 | + 'active:translate-y-px disabled:opacity-60 disabled:cursor-default', |
| 1102 | 1113 | |
| 1114 | /* | |
| 1115 | * ## settings.stx's button vocabulary | |
| 1116 | * | |
| 1117 | * Three paints and four sizes, which is what its 23 raw `<button>` | |
| 1118 | * elements actually were. Measured class string by class string: 14 | |
| 1119 | * distinct strings collapsed to these, with no string left over and no | |
| 1120 | * paint invented. The four controls that are NOT ordinary buttons -- the | |
| 1121 | * theme toggle (`icon-btn`), the channel switch (`tgl`) and the two | |
| 1122 | * segment buttons (`seg`) -- keep their own shortcuts and are untouched. | |
| 1123 | * | |
| 1124 | * Marked `!important` throughout, and this is the reason rather than a | |
| 1125 | * habit. The next step puts `<Button variant="ghost">` behind these, and | |
| 1126 | * the component's own `variantClasses.ghost` and `sizeClasses.md` are | |
| 1127 | * single-class selectors exactly like a shortcut: specificity ties, and | |
| 1128 | * crosswind sorts its output by category rather than by generation order, | |
| 1129 | * so an unmarked override lands PARTIALLY. Half an override is worse than | |
| 1130 | * none, because it reads as working. bughq marked its family for the same | |
| 1131 | * reason and the comment there has the measurement. | |
| 1132 | * | |
| 1133 | * Marked across the whole family, including the `:hover` steps: a marked | |
| 1134 | * base outranks its own unmarked hover. | |
| 1135 | * | |
| 1136 | * The weight lives with the size, not the paint, because the two sizes | |
| 1137 | * that exist in both weights differ ONLY in weight: `btn-md` is the | |
| 1138 | * modal's Cancel and `btn-cta` is every primary action, and both are | |
| 1139 | * px-4 py-2.5 text-sm. | |
| 1140 | */ | |
| 1141 | 'btn-quiet': 'bg-panel border border-line !text-muted ' | |
| 1142 | + '!rounded-[9px] [transition:color_0.15s_ease,border-color_0.15s_ease] ' | |
| 1143 | + 'hover:!text-ink hover:!border-[color-mix(in_srgb,var(--accent)_45%,var(--border))]', | |
| 1144 | ||
| 1145 | 'btn-danger': 'bg-panel border !border-[color-mix(in_srgb,var(--warn)_45%,var(--border))] ' | |
| 1146 | + '!text-warn !rounded-[9px] [transition:all_0.15s_ease] ' | |
| 1147 | + 'hover:!text-accent-ink hover:!bg-warn hover:!border-warn', | |
| 1148 | ||
| 1149 | // The copy button that sits over a code block. Its own entry rather than a | |
| 1150 | // size of btn-quiet: it is positioned, uses the sans stack against the | |
| 1151 | // mono block it overlays, and is the only control in the file at 11px. | |
| 1152 | 'btn-copy': 'absolute right-2 top-2 bg-panel border border-line ' | |
| 1153 | + '!px-[9px] !py-[5px] !text-[11px] !font-medium font-sans leading-none ' | |
| 1154 | + '!text-subtle cursor-pointer !rounded-[7px] ' | |
| 1155 | + '[transition:color_0.15s_ease,border-color_0.15s_ease] ' | |
| 1156 | + 'hover:!text-ink hover:!border-[color-mix(in_srgb,var(--accent)_45%,var(--border))]', | |
| 1157 | ||
| 1158 | 'btn-xs': '!px-2.5 !py-1.5 !text-xs !font-medium', | |
| 1159 | 'btn-sm': '!px-3 !py-2 !text-sm !font-medium', | |
| 1160 | 'btn-md': '!px-4 !py-2.5 !text-sm !font-medium', | |
| 1161 | 'btn-cta': '!px-4 !py-2.5 !text-sm !font-semibold', | |
| 1162 | ||
| 1103 | 1163 | // Shared Button owns semantics and reactive state. These explicit |
| 1104 | 1164 | // overrides preserve the existing auth controls without depending on |
| 1105 | 1165 | // utility order or copying the component into the app. |
| @@ -1137,8 +1197,7 @@ export default { | ||
| 1137 | 1197 | * |
| 1138 | 1198 | * Kept apart from err-box and ok-box rather than folded into them, so the |
| 1139 | 1199 | * composite reads the same way in all three apps that have it: colour is |
| 1140 | * one class, geometry is another, and `class="mb-4 py-2 err-box | |
| 1141 | * notice-box"` says which is which. It also means the confirmation and the | |
| 1200 | * one class, geometry is another, and `class="mb-4 py-2 * err-box notice-box"` says which is which. It also means the confirmation and the | |
| 1142 | 1201 | * error share a box instead of each carrying their own copy of it. |
| 1143 | 1202 | * |
| 1144 | 1203 | * The vertical padding stays at the call site: errors sit at py-2 and |
| @@ -1276,12 +1276,12 @@ onMount(() => { | ||
| 1276 | 1276 | @if (guide.install) |
| 1277 | 1277 | <div class="relative mb-2"> |
| 1278 | 1278 | <div class="overflow-x-auto pl-4 pr-16 py-3 font-mono leading-[1.5] text-[12px] text-muted bg-canvas border border-line rounded-[10px]">{{ guide.install }}</div> |
| 1279 | <button type="button" @click="copyInstall" class="absolute right-2 top-2 px-[9px] py-[5px] font-medium font-sans leading-none text-[11px] text-subtle hover:text-ink bg-panel border border-line rounded-[7px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))] cursor-pointer [transition:color_0.15s_ease,border-color_0.15s_ease]" :text="copiedInstall() ? 'Copied' : 'Copy'">Copy</button> | |
| 1279 | <button type="button" @click="copyInstall" class="btn-copy" :text="copiedInstall() ? 'Copied' : 'Copy'">Copy</button> | |
| 1280 | 1280 | </div> |
| 1281 | 1281 | @endif |
| 1282 | 1282 | <div class="relative"> |
| 1283 | 1283 | <div class="overflow-x-auto pl-4 pr-16 py-3 font-mono leading-[1.5] text-[12px] text-muted whitespace-pre bg-canvas border border-line rounded-[10px]">{{ guide.code }}</div> |
| 1284 | <button type="button" @click="copyCode" class="absolute right-2 top-2 px-[9px] py-[5px] font-medium font-sans leading-none text-[11px] text-subtle hover:text-ink bg-panel border border-line rounded-[7px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))] cursor-pointer [transition:color_0.15s_ease,border-color_0.15s_ease]" :text="copiedCode() ? 'Copied' : 'Copy'">Copy</button> | |
| 1284 | <button type="button" @click="copyCode" class="btn-copy" :text="copiedCode() ? 'Copied' : 'Copy'">Copy</button> | |
| 1285 | 1285 | </div> |
| 1286 | 1286 | <p class="mt-3.5 leading-relaxed text-subtle text-xs"> |
| 1287 | 1287 | @if (guide.native) |
| @@ -1305,9 +1305,9 @@ onMount(() => { | ||
| 1305 | 1305 | </p> |
| 1306 | 1306 | <div class="flex flex-wrap gap-2 items-center"> |
| 1307 | 1307 | <code class="flex-1 px-3.5 py-2.5 min-w-[220px] font-mono text-ink text-sm tracking-[0.01em] truncate bg-canvas border border-line rounded-[10px] focus:border-[color-mix(in_srgb,var(--accent)_55%,var(--border))] focus:outline-none" :text="keyShown()">{{ keyMask }}</code> |
| 1308 | <button @click="keyRevealed.set(!keyRevealed())" class="px-3 py-2 font-medium text-muted text-sm hover:text-ink bg-panel border border-line rounded-[9px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))] [transition:color_0.15s_ease,border-color_0.15s_ease]" :text="keyRevealed() ? 'Hide' : 'Reveal'">Reveal</button> | |
| 1309 | <button @click="copyKey" class="px-3 py-2 font-medium text-muted text-sm hover:text-ink bg-panel border border-line rounded-[9px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))] [transition:color_0.15s_ease,border-color_0.15s_ease]" :text="copiedKey() ? 'Copied' : 'Copy'">Copy</button> | |
| 1310 | <button @click="rotateKey" :disabled="rotating()" class="px-3 py-2 font-medium text-sm text-warn hover:text-accent-ink bg-panel hover:bg-warn border border-[color-mix(in_srgb,var(--warn)_45%,var(--border))] rounded-[9px] hover:border-warn [transition:all_0.15s_ease]">Rotate</button> | |
| 1308 | <button @click="keyRevealed.set(!keyRevealed())" class="btn-quiet btn-sm" :text="keyRevealed() ? 'Hide' : 'Reveal'">Reveal</button> | |
| 1309 | <button @click="copyKey" class="btn-quiet btn-sm" :text="copiedKey() ? 'Copied' : 'Copy'">Copy</button> | |
| 1310 | <button @click="rotateKey" :disabled="rotating()" class="btn-danger btn-sm">Rotate</button> | |
| 1311 | 1311 | </div> |
| 1312 | 1312 | <p class="mt-3.5 leading-relaxed text-subtle text-xs"> |
| 1313 | 1313 | Rotating generates a new key and <span class="text-warn">invalidates the current one immediately</span> — anything still sending the old key will get 401 until you update it (<span class="font-mono">X-LogHQ-Key</span>). |
| @@ -1330,8 +1330,8 @@ onMount(() => { | ||
| 1330 | 1330 | <div :show="!!repo()"> |
| 1331 | 1331 | <div class="flex flex-wrap gap-2 items-center mb-3.5"> |
| 1332 | 1332 | <code class="flex-1 px-3.5 py-2.5 min-w-[220px] font-mono text-ink text-sm truncate bg-canvas border border-line rounded-[10px]" :text="repo()?.full_name">.</code> |
| 1333 | <button @click="verifyRepo" :disabled="repoBusy()" class="px-3 py-2 font-medium text-muted text-sm hover:text-ink bg-panel border border-line rounded-[9px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))] [transition:color_0.15s_ease,border-color_0.15s_ease]">Verify</button> | |
| 1334 | <button @click="disconnectRepo" :disabled="repoBusy()" class="px-3 py-2 font-medium text-sm text-warn hover:text-accent-ink bg-panel hover:bg-warn border border-[color-mix(in_srgb,var(--warn)_45%,var(--border))] rounded-[9px] hover:border-warn [transition:all_0.15s_ease]">Disconnect</button> | |
| 1333 | <button @click="verifyRepo" :disabled="repoBusy()" class="btn-quiet btn-sm">Verify</button> | |
| 1334 | <button @click="disconnectRepo" :disabled="repoBusy()" class="btn-danger btn-sm">Disconnect</button> | |
| 1335 | 1335 | </div> |
| 1336 | 1336 | <dl class="grid gap-y-1.5 grid-cols-2 sm:grid-cols-3 text-xs"> |
| 1337 | 1337 | <div><dt class="text-subtle">Default branch</dt><dd class="font-mono text-muted" :text="repo()?.default_branch || 'unknown'">.</dd></div> |
| @@ -1348,7 +1348,7 @@ onMount(() => { | ||
| 1348 | 1348 | <label class="block mb-1.5 font-medium text-muted text-xs" for="repo-token">GitHub token</label> |
| 1349 | 1349 | <input id="repo-token" :model="repoToken" type="password" autocomplete="off" placeholder="github_pat_..." class="mb-3 px-3.5 py-2.5 w-full font-mono text-ink text-sm bg-canvas border border-line rounded-[10px] focus:border-[color-mix(in_srgb,var(--accent)_55%,var(--border))] focus:outline-none"> |
| 1350 | 1350 | |
| 1351 | <button @click="connectRepo" :disabled="repoBusy()" class="px-3.5 py-2.5 font-semibold text-sm btn-solid">Connect repository</button> | |
| 1351 | <button @click="connectRepo" :disabled="repoBusy()" class="btn-cta btn-solid">Connect repository</button> | |
| 1352 | 1352 | |
| 1353 | 1353 | <p class="mt-3.5 leading-relaxed text-subtle text-xs"> |
| 1354 | 1354 | Use a <span class="text-muted">fine-grained</span> personal access token scoped to this one |
| @@ -1384,7 +1384,7 @@ onMount(() => { | ||
| 1384 | 1384 | <div class="mt-0.5 text-subtle text-xs">Can read the log stream</div> |
| 1385 | 1385 | </div> |
| 1386 | 1386 | <span class="px-[9px] py-0.5 font-semibold text-[11px] text-accent tracking-[0.02em] bg-[color-mix(in_srgb,var(--accent)_14%,var(--panel))] rounded-[999px]">Member</span> |
| 1387 | <button @click="removeMember('')" :disabled="removingMember() === ''" :text="memberBtnLabel('')" class="px-2.5 py-1.5 font-medium text-warn text-xs hover:text-accent-ink bg-panel hover:bg-warn border border-[color-mix(in_srgb,var(--warn)_45%,var(--border))] rounded-[9px] hover:border-warn [transition:all_0.15s_ease]" x-tooltip="Remove">Remove</button> | |
| 1387 | <button @click="removeMember('')" :disabled="removingMember() === ''" :text="memberBtnLabel('')" class="btn-danger btn-xs" x-tooltip="Remove">Remove</button> | |
| 1388 | 1388 | </div> |
| 1389 | 1389 | @endforeach |
| 1390 | 1390 | @foreach (inviteView as i) |
| @@ -1395,8 +1395,8 @@ onMount(() => { | ||
| 1395 | 1395 | <div class="mt-0.5 text-subtle text-xs">{{ i.registered ? 'Has an account — pending accept' : 'Invite sent — awaiting sign-up' }}</div> |
| 1396 | 1396 | </div> |
| 1397 | 1397 | <span class="px-[9px] py-0.5 font-semibold text-[11px] text-muted tracking-[0.02em] bg-[color-mix(in_srgb,var(--text-3)_18%,var(--panel))] rounded-[999px]" x-tooltip="Pending — becomes a member once they accept">Invited</span> |
| 1398 | <button @click="copyJoin('', '')" :text="joinBtnLabel('')" class="px-2.5 py-1.5 font-medium text-muted text-xs hover:text-ink bg-panel border border-line rounded-[9px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))] [transition:color_0.15s_ease,border-color_0.15s_ease]" x-tooltip="Copy join link">Copy link</button> | |
| 1399 | <button @click="removeMember('')" :disabled="removingMember() === ''" :text="memberBtnLabel('')" class="px-2.5 py-1.5 font-medium text-warn text-xs hover:text-accent-ink bg-panel hover:bg-warn border border-[color-mix(in_srgb,var(--warn)_45%,var(--border))] rounded-[9px] hover:border-warn [transition:all_0.15s_ease]" x-tooltip="Revoke invite">Remove</button> | |
| 1398 | <button @click="copyJoin('', '')" :text="joinBtnLabel('')" class="btn-quiet btn-xs" x-tooltip="Copy join link">Copy link</button> | |
| 1399 | <button @click="removeMember('')" :disabled="removingMember() === ''" :text="memberBtnLabel('')" class="btn-danger btn-xs" x-tooltip="Revoke invite">Remove</button> | |
| 1400 | 1400 | </div> |
| 1401 | 1401 | @endforeach |
| 1402 | 1402 | </div> |
| @@ -1404,7 +1404,7 @@ onMount(() => { | ||
| 1404 | 1404 | <div class="mb-3 font-semibold text-sm">Invite a teammate</div> |
| 1405 | 1405 | <div class="flex flex-wrap gap-2 items-center"> |
| 1406 | 1406 | <input :model="inviteEmail" type="email" aria-label="Invite a teammate by email" class="flex-1 px-3.5 py-2.5 min-w-[220px] text-ink text-sm bg-canvas border border-line rounded-[10px] focus:border-[color-mix(in_srgb,var(--accent)_55%,var(--border))] focus:outline-none" placeholder="teammate@company.com" autocomplete="off"> |
| 1407 | <button @click="addInvite" :disabled="inviting()" class="px-4 py-2.5 font-semibold text-accent-ink text-sm bg-accent hover:bg-[color-mix(in_srgb,var(--accent)_88%,#000)] rounded-[10px] disabled:opacity-55 disabled:cursor-default [transition:background_0.15s_ease,opacity_0.15s_ease]" :text="inviting() ? 'Inviting…' : 'Invite'">Invite</button> | |
| 1407 | <button @click="addInvite" :disabled="inviting()" class="btn-cta btn-solid" :text="inviting() ? 'Inviting…' : 'Invite'">Invite</button> | |
| 1408 | 1408 | </div> |
| 1409 | 1409 | <span class="mt-2.5 text-ok text-xs" :show="!!inviteNote()" :class="inviteNoteOk() ? 'mt-2.5 text-xs text-ok' : 'mt-2.5 text-xs text-accent'">{{ inviteNote() }}</span> |
| 1410 | 1410 | <p class="mt-3.5 leading-relaxed text-subtle text-xs"> |
| @@ -1435,7 +1435,7 @@ onMount(() => { | ||
| 1435 | 1435 | <div class="font-medium text-sm truncate">{{ c.label }}</div> |
| 1436 | 1436 | <div class="mt-0.5 font-mono text-subtle text-xs truncate">{{ c.mask }}</div> |
| 1437 | 1437 | </div> |
| 1438 | <button @click="testChannel('')" :text="testLabel('')" class="px-2.5 py-1.5 font-medium text-muted text-xs hover:text-ink bg-panel border border-line rounded-[9px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))] [transition:color_0.15s_ease,border-color_0.15s_ease]" x-tooltip="Send a test message">Test</button> | |
| 1438 | <button @click="testChannel('')" :text="testLabel('')" class="btn-quiet btn-xs" x-tooltip="Send a test message">Test</button> | |
| 1439 | 1439 | {{-- A real <button>, not a <div role="switch">. As a div this had |
| 1440 | 1440 | no tabindex, no key handler and no accessible name: measured |
| 1441 | 1441 | in a browser it reported focusable:false and ariaLabel:null, |
| @@ -1452,7 +1452,7 @@ onMount(() => { | ||
| 1452 | 1452 | match the component instead of the product. The a11y gap was |
| 1453 | 1453 | the defect; the platform element closes it. --}} |
| 1454 | 1454 | <button type="button" role="switch" aria-label="Enable " data-on="" aria-checked="" class="relative flex-none p-0 h-[22px] w-[38px] bg-line border-none rounded-[999px] appearance-none cursor-pointer [transition:background_0.15s_ease] tgl" @click="toggleChannel('', )" :data-on="isChannelOn('', ) ? '1' : '0'" :aria-checked="isChannelOn('', ) ? 'true' : 'false'" x-tooltip="Enable / disable"></button> |
| 1455 | <button @click="deleteChannel('')" :text="deleteLabel('')" class="px-2.5 py-1.5 font-medium text-warn text-xs hover:text-accent-ink bg-panel hover:bg-warn border border-[color-mix(in_srgb,var(--warn)_45%,var(--border))] rounded-[9px] hover:border-warn [transition:all_0.15s_ease]" x-tooltip="Remove">Remove</button> | |
| 1455 | <button @click="deleteChannel('')" :text="deleteLabel('')" class="btn-danger btn-xs" x-tooltip="Remove">Remove</button> | |
| 1456 | 1456 | </div> |
| 1457 | 1457 | @empty |
| 1458 | 1458 | <EmptyState title="No chat alerts yet" description="Add a Slack or Discord webhook below to start getting pinged." /> |
| @@ -1467,7 +1467,7 @@ onMount(() => { | ||
| 1467 | 1467 | <input :model="whUrl" :placeholder="whPlaceholder()" type="url" aria-label="Webhook URL" class="mb-2 px-3.5 py-2.5 w-full font-mono text-ink text-sm bg-canvas border border-line rounded-[10px] focus:border-[color-mix(in_srgb,var(--accent)_55%,var(--border))] focus:outline-none" placeholder="https://hooks.slack.com/services/…" autocomplete="off" spellcheck="false"> |
| 1468 | 1468 | <input :model="whLabel" type="text" aria-label="Channel label" class="mb-3 px-3.5 py-2.5 w-full text-ink text-sm bg-canvas border border-line rounded-[10px] focus:border-[color-mix(in_srgb,var(--accent)_55%,var(--border))] focus:outline-none" placeholder="Label (optional) — e.g. #alerts" maxlength="255"> |
| 1469 | 1469 | <div class="flex gap-3 items-center"> |
| 1470 | <button @click="addChannel" :disabled="adding()" class="px-4 py-2.5 font-semibold text-accent-ink text-sm bg-accent hover:bg-[color-mix(in_srgb,var(--accent)_88%,#000)] rounded-[10px] disabled:opacity-55 disabled:cursor-default [transition:background_0.15s_ease,opacity_0.15s_ease]" :text="adding() ? 'Adding…' : 'Add channel'">Add channel</button> | |
| 1470 | <button @click="addChannel" :disabled="adding()" class="btn-cta btn-solid" :text="adding() ? 'Adding…' : 'Add channel'">Add channel</button> | |
| 1471 | 1471 | <span class="text-ok text-xs" :show="!!whNote()" :class="whNoteOk() ? 'text-xs text-ok' : 'text-xs text-accent'">{{ whNote() }}</span> |
| 1472 | 1472 | </div> |
| 1473 | 1473 | <p :text="whHelp()" class="mt-3.5 leading-relaxed text-subtle text-xs"> |
| @@ -1560,7 +1560,7 @@ onMount(() => { | ||
| 1560 | 1560 | <p class="text-sm text-subtle">Rejects new log entries for <span class="font-mono text-muted">{{ activeProject?.name }}</span> with a 403. Everything already stored is kept — you can unarchive anytime.</p> |
| 1561 | 1561 | @endif |
| 1562 | 1562 | </div> |
| 1563 | <button @click="openArchive" :disabled="archiving()" class="flex-none px-4 py-2.5 font-semibold text-sm text-warn whitespace-nowrap hover:text-accent-ink bg-panel hover:bg-warn border border-[color-mix(in_srgb,var(--warn)_45%,var(--border))] rounded-[9px] hover:border-warn [transition:all_0.15s_ease]">{{ activeProject?.is_active === false ? 'Unarchive' : 'Archive' }}</button> | |
| 1563 | <button @click="openArchive" :disabled="archiving()" class="flex-none whitespace-nowrap btn-cta btn-danger">{{ activeProject?.is_active === false ? 'Unarchive' : 'Archive' }}</button> | |
| 1564 | 1564 | </div> |
| 1565 | 1565 | <div class="h-px bg-[color-mix(in_srgb,var(--warn)_20%,var(--border))]"></div> |
| 1566 | 1566 | {{-- Delete --}} |
| @@ -1569,7 +1569,7 @@ onMount(() => { | ||
| 1569 | 1569 | <div class="mb-0.5 font-semibold text-sm">Delete this project</div> |
| 1570 | 1570 | <p class="text-sm text-subtle">Permanently deletes <span class="font-mono text-muted">{{ activeProject?.name }}</span> and all of its log entries, alert channels, members, and invites. This cannot be undone.</p> |
| 1571 | 1571 | </div> |
| 1572 | <button @click="openDelete" class="flex-none px-4 py-2.5 font-semibold text-sm text-warn whitespace-nowrap hover:text-accent-ink bg-panel hover:bg-warn border border-[color-mix(in_srgb,var(--warn)_45%,var(--border))] rounded-[9px] hover:border-warn [transition:all_0.15s_ease]">Delete</button> | |
| 1572 | <button @click="openDelete" class="flex-none whitespace-nowrap btn-cta btn-danger">Delete</button> | |
| 1573 | 1573 | </div> |
| 1574 | 1574 | </div> |
| 1575 | 1575 | </section> |
| @@ -1592,8 +1592,8 @@ onMount(() => { | ||
| 1592 | 1592 | <label class="block mb-1.5 text-subtle text-xs">Type <span class="font-mono text-muted">{{ projectName }}</span> to confirm</label> |
| 1593 | 1593 | <input :model="modalInput" type="text" :aria-label="modalTitle()" aria-label="Type the project name to confirm" class="mb-4 px-3.5 py-2.5 w-full font-mono text-ink text-sm bg-canvas border border-line rounded-[10px] focus:border-[color-mix(in_srgb,var(--accent)_55%,var(--border))] focus:outline-none" autocomplete="off" autocapitalize="off" spellcheck="false"> |
| 1594 | 1594 | <div class="flex gap-2 items-center justify-end"> |
| 1595 | <button @click="closeModal" type="button" class="px-4 py-2.5 font-medium text-muted text-sm hover:text-ink bg-panel border border-line rounded-[9px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))] [transition:color_0.15s_ease,border-color_0.15s_ease]">Cancel</button> | |
| 1596 | <button @click="confirmModal" :disabled="!modalReady()" :text="modalConfirmLabel()" type="button" class="px-4 py-2.5 font-semibold text-sm text-warn hover:text-accent-ink bg-panel hover:bg-warn border border-[color-mix(in_srgb,var(--warn)_45%,var(--border))] rounded-[9px] hover:border-warn [transition:all_0.15s_ease]">Confirm</button> | |
| 1595 | <button @click="closeModal" type="button" class="btn-md btn-quiet">Cancel</button> | |
| 1596 | <button @click="confirmModal" :disabled="!modalReady()" :text="modalConfirmLabel()" type="button" class="btn-cta btn-danger">Confirm</button> | |
| 1597 | 1597 | </div> |
| 1598 | 1598 | <span class="mt-2.5 text-accent text-xs" :show="!!modalNote()">{{ modalNote() }}</span> |
| 1599 | 1599 | </div> |