maarcadetweet: initial commit
AT Protocol PDS + AppView + Tauri Desktop Client, 160-char post limit. - PDS (Rust + axum + sqlx) - Auth: createAccount, createSession, refreshSession - Records: createRecord, deleteRecord (race-safe via SELECT FOR UPDATE) - Feed: feed.like.create, feed.repost.create - Sync: getRepo, getBlocks, getLatestCommit, getRecord (with MST proof), listRepos - Identity: resolveHandle - MST: spec-conformant (at-mst crate, 27 tests) - Repo: signed commits, TID counter (monotonic, 4096 wrap safe) - AppView (Rust + axum + sqlx) - Jetstream consumer (WebSocket, exponential backoff, 38k+ events indexed) - REST API: timeline/home (graph-aware), profile, search, post (with thread hydration) - Handle-sync worker (did:plc + did:web) - JSONB embed storage + thread columns (migration 0003) - Like/repost counter cache (migration 0004) - Tauri 2 + Svelte 5 Desktop Client - System tray (Show/Compose/Quit menu) - OS notifications (tauri-plugin-notification) - Auto-update (tauri-plugin-updater, placeholder endpoint) - Window-state (tauri-plugin-window-state) - 160-char compose with live counter - Image/Link embed rendering - LocalStorage-persisted like state - Timeline with poll (prepend new posts) - Custom TitleBar (transparent, no decorations) - Orange/IBM Plex Mono maarcade design Tests: 231 Rust + 9 vitest = 240 passed.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
<script lang="ts">
|
||||
type View = "home" | "compose" | "profile" | "search";
|
||||
|
||||
let { current = $bindable<View>("home") }: { current: View } = $props();
|
||||
|
||||
const items: Array<{ id: View; label: string; key: string; icon: string }> = [
|
||||
{ id: "home", label: "home", key: "g h", icon: "home" },
|
||||
{ id: "compose", label: "compose", key: "c", icon: "compose" },
|
||||
{ id: "profile", label: "profile", key: "p", icon: "profile" },
|
||||
{ id: "search", label: "search", key: "/", icon: "search" },
|
||||
];
|
||||
</script>
|
||||
|
||||
<nav class="rail" aria-label="navigation">
|
||||
{#each items as item}
|
||||
<button
|
||||
class="rail__btn"
|
||||
class:active={current === item.id}
|
||||
onclick={() => (current = item.id)}
|
||||
title={`${item.label} (${item.key})`}
|
||||
aria-current={current === item.id ? "page" : undefined}
|
||||
>
|
||||
<span class="icon">
|
||||
{#if item.icon === "home"}
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
|
||||
<path d="M3 11l9-8 9 8v9a2 2 0 0 1-2 2h-3v-7H8v7H5a2 2 0 0 1-2-2z"/>
|
||||
</svg>
|
||||
{:else if item.icon === "compose"}
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round">
|
||||
<text x="3" y="17" font-family="ui-monospace,monospace" font-size="14" font-weight="700" fill="currentColor" stroke="none">>_</text>
|
||||
</svg>
|
||||
{:else if item.icon === "profile"}
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
|
||||
<circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 4-7 8-7s8 3 8 7"/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
|
||||
<circle cx="11" cy="11" r="7"/><path d="m20 20-3-3"/>
|
||||
</svg>
|
||||
{/if}
|
||||
</span>
|
||||
<span class="label">{item.label}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.rail {
|
||||
width: 88px;
|
||||
background: var(--bg);
|
||||
border-right: 1px solid var(--line);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--s-1);
|
||||
padding: var(--s-3) 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.rail__btn {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: var(--text-dim);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--fs-50);
|
||||
padding: var(--s-3) var(--s-2);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-bottom: 1px solid transparent;
|
||||
transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
|
||||
}
|
||||
.rail__btn:hover, .rail__btn:focus-visible {
|
||||
color: var(--text);
|
||||
}
|
||||
.rail__btn.active {
|
||||
color: var(--orange);
|
||||
border-bottom-color: var(--orange);
|
||||
}
|
||||
.icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
.icon :global(svg) {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
.label { letter-spacing: 0.04em; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user