feat: add initial client shell
Client checks / checks (push) Canceled after 0s
Client dependency and security checks / audit (push) Canceled after 0s

This commit is contained in:
2026-10-25 03:52:37 +01:00
parent 67a8a06268
commit 5298c6870b
13 changed files with 1831 additions and 4 deletions
-1
View File
@@ -7,4 +7,3 @@ Read the parent `AGENTS.md`, `PLAN.md`, `TODO.md`, and `docs/wiki/README.md` bef
- Use Prettier for Svelte, JavaScript/TypeScript, JSON, YAML, and Markdown. - Use Prettier for Svelte, JavaScript/TypeScript, JSON, YAML, and Markdown.
- Never store passwords, tokens, or secrets in source, settings, fixtures, logs, or artifacts. - Never store passwords, tokens, or secrets in source, settings, fixtures, logs, or artifacts.
- Do not edit the parent `PLAN.md` without explicit user permission. - Do not edit the parent `PLAN.md` without explicit user permission.
-1
View File
@@ -1,4 +1,3 @@
# Contributing # Contributing
Run formatting, linting, type checks, tests, and the AppImage build before submission. Use deterministic mocked server/API fixtures. Run formatting, linting, type checks, tests, and the AppImage build before submission. Use deterministic mocked server/API fixtures.
-1
View File
@@ -7,4 +7,3 @@ The client is a Svelte-based control surface distributed as an x86-64 AppImage.
Implementation has not started. See the parent workspace's `PLAN.md`, `TODO.md`, and `AGENTS.md` before beginning work. Implementation has not started. See the parent workspace's `PLAN.md`, `TODO.md`, and `AGENTS.md` before beginning work.
Consumes the versioned contracts from the sibling `trixie-api` repository. Consumes the versioned contracts from the sibling `trixie-api` repository.
-1
View File
@@ -1,4 +1,3 @@
# Security # Security
Report vulnerabilities privately through the hosting instance. Never include credentials, tokens, or real user data in issues or fixtures. Report vulnerabilities privately through the hosting instance. Never include credentials, tokens, or real user data in issues or fixtures.
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Trixie</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+1554
View File
File diff suppressed because it is too large Load Diff
+22
View File
@@ -0,0 +1,22 @@
{
"name": "trixy-linux-client",
"private": true,
"version": "0.1.0-dev",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"check": "svelte-check --tsconfig ./tsconfig.json",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
"devDependencies": {
"@tsconfig/svelte": "^5.0.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"prettier": "^3.5.0",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"typescript": "^5.7.0",
"vite": "^6.0.0"
}
}
+54
View File
@@ -0,0 +1,54 @@
<script lang="ts">
let serverAddress = 'http://127.0.0.1:8787';
let connectionState = 'Not connected';
let serverVersion = '—';
async function checkServer() {
connectionState = 'Connecting…';
try {
const response = await fetch(`${serverAddress}/api/v1/server/info`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const info: { server_version: string } = await response.json();
serverVersion = info.server_version;
connectionState = 'Connected';
} catch {
connectionState = 'Server unavailable';
}
}
</script>
<svelte:head>
<meta name="theme-color" content="#2575e6" />
</svelte:head>
<main class="shell">
<header class="topbar">
<div>
<p class="eyebrow">TRIXIE</p>
<h1>Twitch control center</h1>
</div>
<span class:connected={connectionState === 'Connected'} class="status">{connectionState}</span>
</header>
<section class="hero panel">
<div>
<p class="eyebrow">SERVER CONNECTION</p>
<h2>Ready to build the dashboard</h2>
<p class="muted">The client and server now share a versioned readiness contract.</p>
</div>
<div class="connection-form">
<label for="server-address">Server address</label>
<div class="input-row">
<input id="server-address" bind:value={serverAddress} spellcheck="false" />
<button on:click={checkServer}>Test connection</button>
</div>
<small>Server version: {serverVersion}</small>
</div>
</section>
<section class="dashboard-grid">
<article class="panel card"><span class="icon">◈</span><h3>Live dashboard</h3><p>Stream preview, activity, chat, and controls will live here.</p></article>
<article class="panel card"><span class="icon">⌁</span><h3>Bot and plugins</h3><p>Modular commands and safe WASM extensions are planned next.</p></article>
<article class="panel card"><span class="icon">◎</span><h3>Overlays</h3><p>Browser-source alerts will be editable and previewable.</p></article>
</section>
</main>
+8
View File
@@ -0,0 +1,8 @@
import './styles.css';
import App from './App.svelte';
const app = new App({
target: document.getElementById('app')!,
});
export default app;
+154
View File
@@ -0,0 +1,154 @@
:root {
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
color: #f3f5fb;
background: #10131a;
font-synthesis: none;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
background: radial-gradient(circle at 85% 0%, #17325e 0, #10131a 42rem);
}
button,
input {
font: inherit;
}
button {
border: 0;
border-radius: 8px;
padding: 0.7rem 1rem;
color: white;
background: #2575e6;
cursor: pointer;
}
button:hover {
background: #4189ed;
}
.shell {
width: min(1180px, calc(100% - 3rem));
margin: 0 auto;
padding: 2rem 0;
}
.topbar,
.input-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.topbar {
margin-bottom: 2rem;
}
.eyebrow {
margin: 0 0 0.35rem;
color: #78aef5;
font-size: 0.72rem;
font-weight: 800;
letter-spacing: 0.14em;
}
h1,
h2,
h3,
p {
margin-top: 0;
}
h1 {
margin-bottom: 0;
font-size: clamp(1.6rem, 3vw, 2.4rem);
}
h2 {
font-size: clamp(1.4rem, 3vw, 2.1rem);
}
.status {
border: 1px solid #3a4354;
border-radius: 999px;
padding: 0.45rem 0.8rem;
color: #a9b1c2;
font-size: 0.85rem;
}
.status.connected {
border-color: #2f9d70;
color: #77e0ae;
}
.panel {
border: 1px solid #2b3342;
border-radius: 14px;
background: rgba(24, 29, 39, 0.88);
box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.18);
}
.hero {
display: grid;
grid-template-columns: 1.2fr 1fr;
gap: 2rem;
padding: 2rem;
}
.muted,
small {
color: #9aa5b8;
}
.connection-form {
align-self: center;
}
label {
display: block;
margin-bottom: 0.5rem;
color: #c9d2e3;
font-size: 0.85rem;
}
input {
min-width: 0;
flex: 1;
border: 1px solid #3a4354;
border-radius: 8px;
padding: 0.7rem 0.8rem;
color: #f3f5fb;
background: #10131a;
}
.input-row {
justify-content: stretch;
}
small {
display: block;
margin-top: 0.55rem;
}
.dashboard-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin-top: 1rem;
}
.card {
min-height: 180px;
padding: 1.4rem;
}
.card p {
color: #9aa5b8;
line-height: 1.5;
}
.icon {
display: inline-block;
margin-bottom: 1rem;
color: #5d9bf0;
font-size: 1.8rem;
}
@media (max-width: 760px) {
.shell {
width: min(100% - 1.5rem, 600px);
padding-top: 1rem;
}
.hero,
.dashboard-grid {
grid-template-columns: 1fr;
}
.hero {
padding: 1.25rem;
}
.topbar {
align-items: flex-start;
}
}
+5
View File
@@ -0,0 +1,5 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
export default {
preprocess: vitePreprocess(),
};
+16
View File
@@ -0,0 +1,16 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
},
"include": ["src/**/*.ts", "src/**/*.svelte", "vite.config.ts"],
"exclude": ["dist", "node_modules"]
}
+6
View File
@@ -0,0 +1,6 @@
import { svelte } from '@sveltejs/vite-plugin-svelte';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [svelte()],
});