Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions apps/web/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,42 @@ a {
color: var(--text);
}

/* ── Scrollbars — minimal & auto-hiding ───────────────────────────────────────
App-wide: the thumb is invisible at rest and only resolves while the pointer is over (or keyboard
focus is within) the scrolling region, so a panel with an inner scroll never carries a permanent,
heavy gutter. A hairline-scale thumb (4px visible inside a 10px track) keeps it quiet when shown. */
* {
scrollbar-width: thin;
scrollbar-color: transparent transparent;
}
*:hover,
*:focus-within {
scrollbar-color: var(--scrollbar-thumb) transparent;
}
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track,
::-webkit-scrollbar-corner {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: transparent;
border: 3px solid transparent;
border-radius: var(--radius-full);
background-clip: padding-box;
}
*:hover::-webkit-scrollbar-thumb,
*:focus-within::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-thumb-hover);
background-clip: padding-box;
}

/* Keyboard skip link — first focusable element, visible only on focus. */
.skip-link {
position: absolute;
Expand Down
9 changes: 4 additions & 5 deletions apps/web/src/lib/components/Sidebar.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<script lang="ts">
import { enhance } from '$app/forms';
import { page } from '$app/stores';
import { BrandLogo, Button, Pill, StatusDot } from '@telecode/ui';
import { BrandLogo, Button, Pill } from '@telecode/ui';

import { deviceStatus } from '$lib/devices';
import { isActive } from '$lib/nav';
import type { RelayDevice } from '$lib/server/relay-api';
import type { SessionCounts } from '$lib/session-groups';
import type { ConnectionState } from '$lib/session-store';

/**
Expand All @@ -19,13 +18,13 @@
user,
devices,
connection,
counts,
sessionTotal,
onlaunch,
}: {
user: { displayName?: string | null; email?: string | null } | null;
devices: RelayDevice[];
connection: ConnectionState;
counts: SessionCounts;
sessionTotal: number;
onlaunch: () => void;
} = $props();

Expand Down Expand Up @@ -60,7 +59,7 @@
<a class="navlink" href="/" aria-current={isActive(path, '/') ? 'page' : undefined}>
<svg class="nav-icon" viewBox="0 0 15 15" fill="none" aria-hidden="true"><path d="M2 4h11M2 7.5h11M2 11h7" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" /></svg>
<span class="nav-label">Sessions</span>
<span class="nav-count mono">{counts.running + counts.awaiting}</span>
<span class="nav-count mono">{sessionTotal}</span>
</a>
<a class="navlink" href="/devices" aria-current={isActive(path, '/devices') ? 'page' : undefined}>
<svg class="nav-icon" viewBox="0 0 15 15" fill="none" aria-hidden="true"><rect x="1.6" y="2.6" width="11.8" height="8" rx="1.3" stroke="currentColor" stroke-width="1.3" /><path d="M5.2 13h4.6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" /></svg>
Expand Down
17 changes: 9 additions & 8 deletions apps/web/src/routes/(app)/+layout.server.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
import { redirect } from '@sveltejs/kit';

import { listDevices, listRepos } from '$lib/server/relay-api';
import { listDevices, listRepos, listSessions } from '$lib/server/relay-api';
import { getSessionToken } from '$lib/server/session-cookie';

import type { LayoutServerLoad } from './$types';

/**
* The authenticated app shell's shared data: the signed-in user, their paired devices, and their repos
* for the launch drawer. Loaded once for every page under `(app)` (the sidebar, system bar, and launch
* drawer all consume it), so individual pages no longer re-fetch devices/repos. Unauthenticated requests
* are bounced to sign-in here, the single guard for the whole group.
* The authenticated app shell's shared data: the signed-in user, their paired devices, their repos for
* the launch drawer, and the persisted session registry. Loaded once for every page under `(app)` — the
* sidebar (session/device counts), system bar, launch drawer, and dashboard all consume it, so pages no
* longer re-fetch. Unauthenticated requests are bounced to sign-in here, the single guard for the group.
*/
export const load: LayoutServerLoad = async ({ locals, cookies }) => {
if (!locals.user) {
redirect(303, '/signin');
}
const token = getSessionToken(cookies);
const [devices, repoList] = token
? await Promise.all([listDevices(token), listRepos(token)])
: [[], { connected: false, repos: [] }];
const [devices, repoList, sessions] = token
? await Promise.all([listDevices(token), listRepos(token), listSessions(token)])
: [[], { connected: false, repos: [] }, []];
return {
user: locals.user,
devices,
githubConnected: repoList.connected,
repos: repoList.repos,
sessions,
};
};
7 changes: 6 additions & 1 deletion apps/web/src/routes/(app)/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
const device = $derived(data.devices[0] ?? null);
// Live working/blocked tallies for the system bar, straight from the demuxed session map.
const counts = $derived(sessionCounts([...$liveSessions.values()]));
// Total sessions for the sidebar's nav badge: the persisted registry unioned with any launched this
// visit but not yet in it — the same set the dashboard lists, so the badge matches the page.
const sessionTotal = $derived(
new Set([...data.sessions.map((s) => s.id), ...$liveSessions.keys()]).size,
);

// Operator-adjustable sidebar width: seed from storage on the client, persist on every change.
let sidebarWidth = $state(browser ? readSidebarWidth(localStorage) : DEFAULT_SIDEBAR_WIDTH);
Expand Down Expand Up @@ -77,7 +82,7 @@
user={data.user}
devices={data.devices}
connection={$connectionState}
{counts}
{sessionTotal}
onlaunch={openLaunchDrawer}
/>
<SidebarResizer
Expand Down
15 changes: 5 additions & 10 deletions apps/web/src/routes/(app)/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { redirect } from '@sveltejs/kit';

import { destroyRelaySession, listSessions } from '$lib/server/relay-api';
import { destroyRelaySession } from '$lib/server/relay-api';
import { clearSessionCookie, getSessionToken } from '$lib/server/session-cookie';

import type { Actions, PageServerLoad } from './$types';
import type { Actions } from './$types';

/**
* The dashboard's own data: the persisted session registry (survives reloads; live status overlays it in
* the page). The user + devices + repos come from the `(app)` layout load, so they aren't re-fetched here.
* The dashboard reads the persisted session registry (`data.sessions`), the user, devices, and repos from
* the `(app)` layout load — the shell counts sessions too, so the list lives there and isn't re-fetched
* here. This module owns only the dashboard's logout action.
*/
export const load: PageServerLoad = async ({ cookies }) => {
const token = getSessionToken(cookies);
const sessions = token ? await listSessions(token) : [];
return { sessions };
};

export const actions: Actions = {
logout: async ({ cookies }) => {
const token = getSessionToken(cookies);
Expand Down
5 changes: 5 additions & 0 deletions packages/ui/src/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@
--border-strong: var(--ink-line-strong);
--focus-ring: var(--accent-ring);

/* Scrollbar — minimal & auto-hiding; the thumb resolves only on hover/focus. References semantic
tokens (resolved lazily) so it re-themes for free under [data-theme='light']. */
--scrollbar-thumb: var(--border-strong);
--scrollbar-thumb-hover: var(--text-muted);

--primary: var(--accent);
--primary-text: var(--text-on-accent);
--neutral: #eceef1;
Expand Down
Loading