slack status without the slack status.zzstoatzz.io
hatk statusphere
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix link previews, mobile layout, and /@handle routing

- remove static OG tags from app.html so SSR tags take precedence
- add /@handle route that resolves to /profile/{did}
- update internal links to use /@handle format
- mobile-first CSS: header overflow, touch-friendly buttons, responsive sizing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+51 -17
+24 -7
app/app.css
··· 60 60 margin-bottom: 2rem; 61 61 padding-bottom: 1rem; 62 62 border-bottom: 1px solid var(--border); 63 + gap: 0.75rem; 63 64 } 64 65 65 66 header h1 { 66 67 font-size: 1.5rem; 67 68 font-weight: 600; 69 + min-width: 0; 70 + overflow: hidden; 71 + text-overflow: ellipsis; 72 + white-space: nowrap; 68 73 } 69 74 70 75 header h1 a { color: var(--text); } ··· 72 77 73 78 nav { 74 79 display: flex; 75 - gap: 1rem; 80 + gap: 0.5rem; 76 81 align-items: center; 82 + flex-shrink: 0; 77 83 } 78 84 79 85 .nav-btn { ··· 445 451 cursor: pointer; 446 452 padding: 0.25rem; 447 453 border-radius: 4px; 448 - opacity: 0; 449 454 transition: opacity 0.15s, color 0.15s; 450 455 flex-shrink: 0; 451 456 position: relative; 452 457 } 453 458 454 - .status-item:hover .share-btn, 455 - .status-item:hover .delete-btn { opacity: 1; } 459 + @media (hover: hover) { 460 + .share-btn, 461 + .delete-btn { opacity: 0; } 462 + .status-item:hover .share-btn, 463 + .status-item:hover .delete-btn { opacity: 1; } 464 + } 456 465 .share-btn:hover { color: var(--accent); } 457 466 .delete-btn:hover { color: #ef4444; } 458 467 .share-btn.copied { color: var(--accent); opacity: 1; } ··· 732 741 733 742 @media (max-width: 480px) { 734 743 .app-shell { padding: 1rem 0.75rem; } 735 - .profile-card { padding: 1.5rem 1rem; } 744 + header { margin-bottom: 1.25rem; } 745 + header h1 { font-size: 1.1rem; } 746 + nav { gap: 0.25rem; } 747 + .nav-btn { padding: 0.375rem; } 748 + .theme-toggle { padding: 0.375rem; font-size: 0.875rem; } 749 + .profile-card { padding: 1.25rem 1rem; } 736 750 .big-emoji { font-size: 3rem; } 737 751 .big-emoji img { width: 3rem; height: 3rem; } 738 - .emoji-picker { width: calc(100vw - 2rem); } 739 - .settings-modal { width: calc(100vw - 2rem); } 752 + .current-text { font-size: 1.1rem; } 753 + .status-item { padding: 0.75rem; gap: 0.75rem; } 754 + .emoji-picker { width: calc(100vw - 1.5rem); max-height: calc(100vh - 2rem); } 755 + .settings-modal { width: calc(100vw - 1.5rem); } 756 + .form-actions { flex-direction: column; } 740 757 }
-8
app/app.html
··· 5 5 <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 6 <link rel="icon" type="image/svg+xml" href="%sveltekit.assets%/favicon.svg" /> 7 7 <meta name="theme-color" content="#0a0a0a" /> 8 - <meta property="og:type" content="website" /> 9 - <meta property="og:title" content="status" /> 10 - <meta property="og:description" content="share your status" /> 11 - <meta property="og:url" content="https://status.zzstoatzz.io" /> 12 - <meta property="og:site_name" content="status" /> 13 - <meta name="twitter:card" content="summary" /> 14 - <meta name="twitter:title" content="status" /> 15 - <meta name="twitter:description" content="share your status" /> 16 8 <title>status</title> 17 9 %sveltekit.head% 18 10 </head>
+3
app/lib/components/Header.svelte
··· 40 40 </a> 41 41 {:else if currentPage.startsWith('/feed')} 42 42 global feed 43 + {:else if currentPage.startsWith('/@')} 44 + {@const handle = decodeURIComponent(currentPage.slice(2))} 45 + <a href="https://bsky.app/profile/{handle}" target="_blank">@{handle}</a> 43 46 {:else if currentPage.startsWith('/profile/')} 44 47 profile 45 48 {:else}
+1 -1
app/lib/components/StatusCard.svelte
··· 59 59 <div class="content"> 60 60 <div> 61 61 {#if showAuthor && (status.handle || status.did)} 62 - <a href="/profile/{status.did}" class="author">@{status.handle ?? status.did?.slice(0, 18)}</a> 62 + <a href="/@{status.handle ?? status.did}" class="author">@{status.handle ?? status.did?.slice(0, 18)}</a> 63 63 {/if} 64 64 {#if status.text} 65 65 <span class="text">{@html parseLinks(status.text)}</span>
+22
app/routes/@[handle]/+page.server.ts
··· 1 + import { redirect } from "@sveltejs/kit"; 2 + import { callXrpc } from "$hatk/client"; 3 + import type { PageServerLoad } from "./$types"; 4 + 5 + export const load: PageServerLoad = async ({ params }) => { 6 + const handle = decodeURIComponent(params.handle); 7 + 8 + try { 9 + const res = await callXrpc("dev.hatk.getFeed", { 10 + feed: "actor", 11 + actor: handle, 12 + limit: 1, 13 + }); 14 + const did = res.items?.[0]?.did; 15 + if (did) { 16 + redirect(302, `/profile/${encodeURIComponent(did)}`); 17 + } 18 + } catch {} 19 + 20 + // no statuses found — redirect anyway, profile page will show "no statuses yet" 21 + redirect(302, `/profile/${encodeURIComponent(handle)}`); 22 + };
+1 -1
app/routes/status/[did]/[rkey]/+page.svelte
··· 62 62 </div> 63 63 </div> 64 64 <div class="center"> 65 - <a href="/profile/{data.did}" class="view-profile-link">view all statuses</a> 65 + <a href="/@{handle}" class="view-profile-link">view all statuses</a> 66 66 </div> 67 67 {:else} 68 68 <div class="center">status not found</div>