feat: refine ISAC design and prepare production deployment
Validate and deploy production / validate (push) Successful in 18s
Validate and deploy staging / validate (push) Successful in 18s
Validate and deploy production / deploy (push) Successful in 1m1s
Validate and deploy staging / deploy (push) Successful in 28s

This commit is contained in:
JB
2026-07-26 12:25:47 -04:00
parent 75faef50b8
commit 2e868d86ff
153 changed files with 64660 additions and 264 deletions
+330 -127
View File
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
<script lang="ts">
import { page } from '$app/state';
let { compact = false }: { compact?: boolean } = $props();
const permissions = $derived((page.data.permissions as string[] | undefined) ?? []);
const can = (permission: string) => permissions.includes('*') || permissions.includes(permission);
const publishesDirectly = $derived(can('data.manage'));
const canSubmit = $derived(publishesDirectly || (can('data.propose') && Boolean(page.data.contributorMode?.enabled)));
</script>
{#if canSubmit}
<p class="mutation-consequence" class:compact class:direct={publishesDirectly} role="note">
<b>{publishesDirectly ? 'DIRECT PUBLISH' : 'OVERSIGHT REVIEW'}</b>
<span>{publishesDirectly ? 'Changes publish immediately and create an audit event.' : 'Saving creates a proposal; live data remains unchanged until Oversight approves it.'}</span>
</p>
{/if}
+12 -9
View File
@@ -65,6 +65,7 @@
);
const start = $derived(pagination.total ? (pagination.page - 1) * pagination.perPage + 1 : 0);
const end = $derived(Math.min(pagination.total, pagination.page * pagination.perPage));
const hasMultiplePages = $derived(pagination.pageCount > 1);
</script>
<nav class="registry-pagination" aria-label="Registry pagination">
@@ -77,13 +78,15 @@
</form>
{/if}
<div class="pagination-range"><span>DISPLAYING</span><b>{start}–{end}</b><span>OF {pagination.total}</span></div>
<div class="pagination-pages">
<a class:disabled={pagination.page === 1} aria-disabled={pagination.page === 1} href={href(Math.max(1, pagination.page - 1))}>← PREV</a>
{#each visiblePages as page, index}
{#if index > 0 && page - visiblePages[index - 1] > 1}<span class="pagination-gap">···</span>{/if}
<a class:active={page === pagination.page} aria-current={page === pagination.page ? 'page' : undefined} href={href(page)}>{page}</a>
{/each}
<a class:disabled={pagination.page === pagination.pageCount} aria-disabled={pagination.page === pagination.pageCount} href={href(Math.min(pagination.pageCount, pagination.page + 1))}>NEXT →</a>
</div>
<div class="pagination-sizes"><span>ROWS</span>{#each [10, 25, 50, 100] as size}<a class:active={size === pagination.perPage} href={href(1, size)}>{size}</a>{/each}</div>
{#if hasMultiplePages}
<div class="pagination-pages">
<a class:disabled={pagination.page === 1} aria-disabled={pagination.page === 1} href={href(Math.max(1, pagination.page - 1))}>← PREV</a>
{#each visiblePages as page, index}
{#if index > 0 && page - visiblePages[index - 1] > 1}<span class="pagination-gap">···</span>{/if}
<a class:active={page === pagination.page} aria-current={page === pagination.page ? 'page' : undefined} href={href(page)}>{page}</a>
{/each}
<a class:disabled={pagination.page === pagination.pageCount} aria-disabled={pagination.page === pagination.pageCount} href={href(Math.min(pagination.pageCount, pagination.page + 1))}>NEXT →</a>
</div>
<div class="pagination-sizes"><span>ROWS</span>{#each [10, 25, 50, 100] as size}<a class:active={size === pagination.perPage} href={href(1, size)}>{size}</a>{/each}</div>
{/if}
</nav>
+7
View File
@@ -1,4 +1,11 @@
<script lang="ts">
import '@fontsource/barlow/400.css';
import '@fontsource/barlow/600.css';
import '@fontsource/barlow/700.css';
import '@fontsource/barlow/800.css';
import '@fontsource/barlow/900.css';
import '@fontsource/source-serif-4/400.css';
import '@fontsource/source-serif-4/600.css';
import '../app.css';
import AppShell from '$lib/components/AppShell.svelte';
import ToastViewport from '$lib/components/ToastViewport.svelte';
+71 -37
View File
@@ -1,5 +1,7 @@
<script lang="ts">
import { page } from '$app/state';
import { onMount } from 'svelte';
import MutationConsequence from '$lib/components/MutationConsequence.svelte';
let { data, children } = $props();
const can = (permission: string) => data.permissions.includes('*') || data.permissions.includes(permission);
const canSeeData = $derived(['data.view', 'data.manage', 'data.propose', 'data.approve'].some(can));
@@ -10,33 +12,60 @@
const dataModeClass = $derived(dataMode.toLowerCase().replace(' ', '-'));
let readOnlyBlocked = $state(false);
const dataSections = [
{ href: '/isac/configuration', label: 'Global configuration', code: 'CONF', module: 'configuration' },
{ href: '/isac/attributes', label: 'Attributes', code: 'ATTR', module: 'attributes' },
{ href: '/isac/talents', label: 'Talents', code: 'TLNT', module: 'talents' },
{ href: '/isac/brands', label: 'Brand sets', code: 'BRND', module: 'brands' },
{ href: '/isac/gear', label: 'Gear', code: 'GEAR', module: 'gear' },
{ href: '/isac/weapons', label: 'Weapons', code: 'WPN', module: 'weapons' },
{ href: '/isac/weaponmods', label: 'Weapon Mods', code: 'WMOD', module: 'weaponmods' },
{ href: '/isac/gearmods', label: 'Gear Mods', code: 'GMOD', module: 'gearmods' },
{ href: '/isac/protocols', label: 'OS Protocols', code: 'OSPR', module: 'protocols' },
{ href: '/isac/skillchips', label: 'Skill Chips', code: 'CHIP', module: 'skillchips' }
{ href: '/isac/configuration', label: 'Global configuration', module: 'configuration' },
{ href: '/isac/attributes', label: 'Attributes', module: 'attributes' },
{ href: '/isac/talents', label: 'Talents', module: 'talents' },
{ href: '/isac/brands', label: 'Brand sets', module: 'brands' },
{ href: '/isac/gear', label: 'Gear', module: 'gear' },
{ href: '/isac/weapons', label: 'Weapons', module: 'weapons' },
{ href: '/isac/weaponmods', label: 'Weapon Mods', module: 'weaponmods' },
{ href: '/isac/gearmods', label: 'Gear Mods', module: 'gearmods' },
{ href: '/isac/protocols', label: 'OS Protocols', module: 'protocols' },
{ href: '/isac/skillchips', label: 'Skill Chips', module: 'skillchips' }
];
const dataModulePage = $derived(dataSections.some((section) => page.url.pathname === section.href));
const userSections = [
{ href: '/isac/users', label: 'User operations', code: 'USER', permission: 'users.view' },
{ href: '/isac/roles', label: 'Roles', code: 'ROLE', permission: 'users.roles' }
{ href: '/isac/users', label: 'User operations', permission: 'users.view' },
{ href: '/isac/roles', label: 'Roles', permission: 'users.roles' }
];
const visibleUserSections = $derived(userSections.filter((section) => can(section.permission)));
const oversightSections: Array<{ href: string; label: string; code: string; permission: string; reviewKey?: 'data' | 'avatars' }> = [
{ href: '/isac/data-moderation', label: 'Data Oversight', code: 'OVST', permission: 'data.approve', reviewKey: 'data' },
{ href: '/isac/avatar-oversight', label: 'Avatar Oversight', code: 'AVTR', permission: 'avatars.review', reviewKey: 'avatars' },
{ href: '/isac/audit', label: 'Audit history', code: 'AUDT', permission: 'audit.view' }
const oversightSections: Array<{ href: string; label: string; permission: string; reviewKey?: 'data' | 'avatars' }> = [
{ href: '/isac/data-moderation', label: 'Data Oversight', permission: 'data.approve', reviewKey: 'data' },
{ href: '/isac/avatar-oversight', label: 'Avatar Oversight', permission: 'avatars.review', reviewKey: 'avatars' },
{ href: '/isac/audit', label: 'Audit history', permission: 'audit.view' }
];
const visibleOversightSections = $derived(oversightSections.filter((section) => can(section.permission)));
const systemSections = [
{ href: '/isac/system-messages', label: 'Automated messages', code: 'MSG', permission: 'system.notifications.manage' },
{ href: '/isac/metrics', label: 'System metrics', code: 'MTRX', permission: 'metrics.view' }
{ href: '/isac/system-messages', label: 'Automated messages', permission: 'system.notifications.manage' },
{ href: '/isac/metrics', label: 'System metrics', permission: 'metrics.view' }
];
const visibleSystemSections = $derived(systemSections.filter((section) => can(section.permission)));
type SidebarGroup = 'users' | 'data' | 'oversight' | 'system';
let moduleQuery = $state('');
let groupOpen = $state<Record<SidebarGroup, boolean>>({ users: false, data: false, oversight: false, system: false });
const matchesModuleQuery = (label: string) => label.toLocaleLowerCase().includes(moduleQuery.trim().toLocaleLowerCase());
const filteredUserSections = $derived(visibleUserSections.filter((section) => matchesModuleQuery(section.label)));
const filteredDataSections = $derived(dataSections.filter((section) => matchesModuleQuery(section.label)));
const filteredOversightSections = $derived(visibleOversightSections.filter((section) => matchesModuleQuery(section.label)));
const filteredSystemSections = $derived(visibleSystemSections.filter((section) => matchesModuleQuery(section.label)));
const activeGroup = (pathname: string): SidebarGroup =>
pathname === '/isac' ? (visibleUserSections.length ? 'users' : canSeeData ? 'data' : visibleOversightSections.length ? 'oversight' : 'system')
: userSections.some((section) => pathname.startsWith(section.href)) ? 'users'
: dataSections.some((section) => pathname === section.href) || pathname === '/isac/contributor-guidelines' ? 'data'
: oversightSections.some((section) => pathname.startsWith(section.href)) ? 'oversight'
: 'system';
const setGroupOpen = (group: SidebarGroup, open: boolean) => {
groupOpen = { ...groupOpen, [group]: open };
localStorage.setItem('shd.isac.sidebar-groups', JSON.stringify(groupOpen));
};
onMount(() => {
try {
const remembered = JSON.parse(localStorage.getItem('shd.isac.sidebar-groups') ?? '{}');
groupOpen = { ...groupOpen, ...remembered, [activeGroup(page.url.pathname)]: true };
} catch {
groupOpen = { ...groupOpen, [activeGroup(page.url.pathname)]: true };
}
});
const selectNumericValue = (event: FocusEvent) => {
const input = event.target;
if (input instanceof HTMLInputElement && input.type === 'number' && !input.disabled) input.select();
@@ -46,32 +75,36 @@
<main class="admin-page">
<header class="admin-command">
<div class="command-title"><p class="eyebrow"><span></span> INTELLIGENT SYSTEM ANALYTIC COMPUTER</p><h1>ISAC</h1><p>SHD.build operations and intelligence control surface</p></div>
<div class="command-status"><div><i></i><span>SYSTEM</span><strong>ONLINE</strong></div><div><span>ACCESS</span><strong>AUTHORIZED</strong></div><div><span>ENVIRONMENT</span><strong>LOCAL</strong></div><div class="data-mode-status" class:admin={dataModeClass === 'admin'} class:data-manager={dataModeClass === 'data-manager'} class:oversight={dataModeClass === 'oversight'} class:contributor={dataModeClass === 'contributor'} class:read-only={dataModeClass === 'read-only'}><span>DATA MODE</span><strong>{dataMode}</strong></div></div>
<div class="command-status compact-status" aria-label="ISAC system status"><span><i></i>SYSTEM <strong>ONLINE</strong></span><span>ACCESS <strong>AUTHORIZED</strong></span><span>ENV <strong>LOCAL</strong></span><span class="data-mode-status" class:admin={dataModeClass === 'admin'} class:data-manager={dataModeClass === 'data-manager'} class:oversight={dataModeClass === 'oversight'} class:contributor={dataModeClass === 'contributor'} class:read-only={dataModeClass === 'read-only'}>MODE <strong>{dataMode}</strong></span></div>
</header>
<div class="admin-dashboard">
<aside class="admin-sidebar">
{#if visibleUserSections.length}
<div class="sidebar-label"><span>USER OPERATIONS</span><b>{String(visibleUserSections.length).padStart(2, '0')} MODULE{visibleUserSections.length === 1 ? '' : 'S'}</b></div>
<nav aria-label="User operations">{#each visibleUserSections as section}<a class:active={page.url.pathname.startsWith(section.href)} href={section.href}><small>{section.code}</small><span>{section.label}</span><b>›</b></a>{/each}</nav>
<label class="sidebar-module-search">
<span>FIND MODULE</span>
<input type="search" bind:value={moduleQuery} placeholder="Search ISAC…" aria-label="Search authorized ISAC modules"/>
</label>
{#if visibleUserSections.length && (!moduleQuery || filteredUserSections.length)}
<button class="sidebar-label" type="button" aria-expanded={groupOpen.users || Boolean(moduleQuery)} aria-controls="isac-nav-users" onclick={() => setGroupOpen('users', !groupOpen.users)}><span>USER OPERATIONS</span><b>{String(visibleUserSections.length).padStart(2, '0')} <i>{groupOpen.users || moduleQuery ? '−' : '+'}</i></b></button>
{#if groupOpen.users || moduleQuery}<nav id="isac-nav-users" aria-label="User operations">{#each filteredUserSections as section}<a class:active={page.url.pathname.startsWith(section.href)} href={section.href}><span>{section.label}</span><b>›</b></a>{/each}</nav>{/if}
{/if}
{#if canSeeData}
<div class="sidebar-label" class:section-label={visibleUserSections.length > 0}><span>DATA MANAGEMENT</span><b>{String(dataSections.length).padStart(2, '0')} MODULES</b></div>
<nav aria-label="Data management">
{#each dataSections as section}<a class:active={page.url.pathname === section.href} class:has-pending={Boolean(data.pendingProposals[section.module])} href={section.href}><small>{section.code}</small><span>{section.label}</span>{#if data.pendingProposals[section.module]}<em aria-label={`${data.pendingProposals[section.module]} pending change proposal${data.pendingProposals[section.module] === 1 ? '' : 's'}`} title="Pending change proposals">{data.pendingProposals[section.module]}</em>{/if}<b>›</b></a>{/each}
</nav>
{#if canSeeData && (!moduleQuery || filteredDataSections.length)}
<button class="sidebar-label" class:section-label={visibleUserSections.length > 0} type="button" aria-expanded={groupOpen.data || Boolean(moduleQuery)} aria-controls="isac-nav-data" onclick={() => setGroupOpen('data', !groupOpen.data)}><span>DATA MANAGEMENT</span><b>{String(dataSections.length).padStart(2, '0')} <i>{groupOpen.data || moduleQuery ? '−' : '+'}</i></b></button>
{#if groupOpen.data || moduleQuery}<nav id="isac-nav-data" aria-label="Data management">
{#each filteredDataSections as section}<a class:active={page.url.pathname === section.href} class:has-pending={Boolean(data.pendingProposals[section.module])} href={section.href}><span>{section.label}</span>{#if data.pendingProposals[section.module]}<em aria-label={`${data.pendingProposals[section.module]} pending change proposal${data.pendingProposals[section.module] === 1 ? '' : 's'}`} title="Pending change proposals">{data.pendingProposals[section.module]}</em>{/if}<b>›</b></a>{/each}
</nav>{/if}
{/if}
{#if visibleOversightSections.length}
<div class="sidebar-label section-label"><span>SYSTEM OVERSIGHT</span><b>{String(visibleOversightSections.length).padStart(2, '0')} MODULE{visibleOversightSections.length === 1 ? '' : 'S'}</b></div>
<nav aria-label="System oversight">{#each visibleOversightSections as section}
{#if visibleOversightSections.length && (!moduleQuery || filteredOversightSections.length)}
<button class="sidebar-label section-label" type="button" aria-expanded={groupOpen.oversight || Boolean(moduleQuery)} aria-controls="isac-nav-oversight" onclick={() => setGroupOpen('oversight', !groupOpen.oversight)}><span>SYSTEM OVERSIGHT</span><b>{String(visibleOversightSections.length).padStart(2, '0')} <i>{groupOpen.oversight || moduleQuery ? '−' : '+'}</i></b></button>
{#if groupOpen.oversight || moduleQuery}<nav id="isac-nav-oversight" aria-label="System oversight">{#each filteredOversightSections as section}
{@const reviewCount = section.reviewKey ? Number(data.oversightReviewCounts[section.reviewKey] ?? 0) : 0}
<a class:active={page.url.pathname.startsWith(section.href)} class:has-pending={reviewCount > 0} href={section.href}><small>{section.code}</small><span>{section.label}</span>{#if reviewCount}<em aria-label={`${reviewCount} item${reviewCount === 1 ? '' : 's'} awaiting review`} title="Awaiting review">{reviewCount}</em>{/if}<b>›</b></a>
{/each}</nav>
<a class:active={page.url.pathname.startsWith(section.href)} class:has-pending={reviewCount > 0} href={section.href}><span>{section.label}</span>{#if reviewCount}<em aria-label={`${reviewCount} item${reviewCount === 1 ? '' : 's'} awaiting review`} title="Awaiting review">{reviewCount}</em>{/if}<b>›</b></a>
{/each}</nav>{/if}
{/if}
{#if visibleSystemSections.length}
<div class="sidebar-label section-label"><span>SYSTEM CONFIGURATION</span><b>{String(visibleSystemSections.length).padStart(2, '0')} MODULE{visibleSystemSections.length === 1 ? '' : 'S'}</b></div>
<nav aria-label="System configuration">{#each visibleSystemSections as section}
<a class:active={page.url.pathname.startsWith(section.href)} href={section.href}><small>{section.code}</small><span>{section.label}</span><b>›</b></a>
{/each}</nav>
{#if visibleSystemSections.length && (!moduleQuery || filteredSystemSections.length)}
<button class="sidebar-label section-label" type="button" aria-expanded={groupOpen.system || Boolean(moduleQuery)} aria-controls="isac-nav-system" onclick={() => setGroupOpen('system', !groupOpen.system)}><span>SYSTEM CONFIGURATION</span><b>{String(visibleSystemSections.length).padStart(2, '0')} <i>{groupOpen.system || moduleQuery ? '−' : '+'}</i></b></button>
{#if groupOpen.system || moduleQuery}<nav id="isac-nav-system" aria-label="System configuration">{#each filteredSystemSections as section}
<a class:active={page.url.pathname.startsWith(section.href)} href={section.href}><span>{section.label}</span><b>›</b></a>
{/each}</nav>{/if}
{/if}
<div class="sidebar-foot"><span>ISAC NODE</span><strong>NYC-01</strong><small>Access policy synchronized</small></div>
</aside>
@@ -85,6 +118,7 @@
{#if dataReadOnly}<div class="data-access-banner"><div><b>READ-ONLY DATA ACCESS</b><span>You can inspect registries without submitting changes.</span></div>{#if can('data.propose')}<a class="mode-toggle" href={`/isac/contributor-guidelines?returnTo=${encodeURIComponent(page.url.pathname + page.url.search)}`}>ENABLE CONTRIBUTOR MODE →</a>{/if}</div>
{:else if contributorEnabled && !guidelinesPage && can('data.propose') && !can('data.manage')}<div class="data-access-banner contributor"><div><b>CONTRIBUTOR MODE</b><span>Suggestions enter moderation and do not immediately change live data.</span></div><form method="POST" action="/isac/contributor-guidelines?/disable"><input type="hidden" name="returnTo" value={page.url.pathname + page.url.search}/><button class="mode-toggle" type="submit">RETURN TO READ-ONLY</button></form></div>{/if}
{#if readOnlyBlocked}<p class="error data-access-error" role="alert">This account has read-only game-data access.</p>{/if}
{#if dataModulePage && !dataReadOnly}<div class="mutation-policy-strip"><MutationConsequence/></div>{/if}
{@render children()}
</section>
</div>