Compare commits
No commits in common. "d91dc1d5e2ac0973041d37990ea7d95130055538" and "a2a7eb6cd77278d3d342bc4e2887bac5c478136f" have entirely different histories.
d91dc1d5e2
...
a2a7eb6cd7
3 changed files with 2 additions and 453 deletions
|
|
@ -1,386 +0,0 @@
|
||||||
# sanctum-web Layout Redesign Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Fork stoatchat/for-web into sanctum-web, fix the root layout so the message area fills the screen properly, and deploy it to mithraic.space/app via Forgejo CI.
|
|
||||||
|
|
||||||
**Architecture:** The root layout lives in two files — `Interface.tsx` (the grid shell) and `Sidebar.tsx` (server list + channel list). Both use Panda CSS (`styled-system/jsx`). We replace the loose flex layout with an explicit CSS Grid, add `min-width: 0` guards on grid children, and wire up a Forgejo Actions pipeline that builds and rsyncs to the VPS on every push to main.
|
|
||||||
|
|
||||||
**Tech Stack:** Solid.js, Vite, TypeScript, Panda CSS (`styled-system/jsx`), pnpm monorepo, Forgejo Actions, rsync
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## File Map
|
|
||||||
|
|
||||||
| Action | Path | Purpose |
|
|
||||||
|---|---|---|
|
|
||||||
| Clone | `git.mithraic.cloud/ad3laid3/sanctum-web` | The fork (already created) |
|
|
||||||
| Modify | `packages/client/src/Interface.tsx` | Root grid shell — Layout + Content styled components |
|
|
||||||
| Modify | `packages/client/src/interface/Sidebar.tsx` | Server list + channel list sizing |
|
|
||||||
| Create | `.forgejo/workflows/deploy.yml` | CI: build + rsync to VPS on push to main |
|
|
||||||
| Create | `packages/client/.env` | API URLs pointing at mithraic.space services |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 1: Clone the fork and add upstream remote
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Working directory: wherever you keep projects locally
|
|
||||||
|
|
||||||
- [ ] **Step 1: Clone the fork**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://git.mithraic.cloud/ad3laid3/sanctum-web
|
|
||||||
cd sanctum-web
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Add the upstream so you can pull fixes from stoatchat later**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git remote add upstream https://github.com/stoatchat/for-web
|
|
||||||
git fetch upstream
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Install dependencies**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm install
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 4: Confirm dev server starts**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd packages/client
|
|
||||||
pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: Vite dev server starts, browser opens the app (will show login screen since no backend is configured yet — that's fine).
|
|
||||||
|
|
||||||
- [ ] **Step 5: Commit the baseline (upstream content as-is)**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ../..
|
|
||||||
git add .
|
|
||||||
git commit -m "chore: initial fork from stoatchat/for-web"
|
|
||||||
git push origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 2: Configure environment for mithraic.space
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `packages/client/.env`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Create the env file**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cat > packages/client/.env << 'EOF'
|
|
||||||
VITE_API_URL=https://mithraic.space/api
|
|
||||||
VITE_WS_URL=wss://mithraic.space/ws
|
|
||||||
VITE_MEDIA_URL=https://mithraic.space/media
|
|
||||||
VITE_PROXY_URL=https://mithraic.space/proxy
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Confirm build succeeds with these env vars**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd packages/client
|
|
||||||
pnpm build
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: `dist/` folder is created with no errors.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ../..
|
|
||||||
git add packages/client/.env
|
|
||||||
git commit -m "chore: configure env for mithraic.space"
|
|
||||||
git push origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 3: Fix the root layout in Interface.tsx
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `packages/client/src/Interface.tsx`
|
|
||||||
|
|
||||||
The current `Layout` uses `display: flex` with no explicit column widths, and `Content` uses `width: 100%` which doesn't fill remaining space properly in a flex context. The fix: switch Layout to CSS Grid with `auto 1fr` columns, and replace `width: 100%` on Content with `minWidth: 0` so it respects the grid.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Open `packages/client/src/Interface.tsx` and find the two styled components at the bottom of the file**
|
|
||||||
|
|
||||||
They look like this:
|
|
||||||
```typescript
|
|
||||||
const Layout = styled("div", {
|
|
||||||
base: {
|
|
||||||
display: "flex",
|
|
||||||
height: "100%",
|
|
||||||
minWidth: 0,
|
|
||||||
},
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
|
|
||||||
const Content = styled("div", {
|
|
||||||
base: {
|
|
||||||
background: "var(--md-sys-color-surface-container-low)",
|
|
||||||
display: "flex",
|
|
||||||
width: "100%",
|
|
||||||
minWidth: 0,
|
|
||||||
},
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Replace `Layout` base styles — switch from flex to grid**
|
|
||||||
|
|
||||||
Change the `Layout` styled component's `base` to:
|
|
||||||
```typescript
|
|
||||||
const Layout = styled("div", {
|
|
||||||
base: {
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "auto 1fr",
|
|
||||||
height: "100%",
|
|
||||||
overflow: "hidden",
|
|
||||||
},
|
|
||||||
variants: {
|
|
||||||
disconnected: {
|
|
||||||
true: {
|
|
||||||
color: "var(--md-sys-color-on-primary-container)",
|
|
||||||
background: "var(--md-sys-color-primary-container)",
|
|
||||||
},
|
|
||||||
false: {
|
|
||||||
color: "var(--md-sys-color-outline)",
|
|
||||||
background: "var(--md-sys-color-surface-container-high)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Replace `Content` base styles — remove width:100%, add minWidth:0**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const Content = styled("div", {
|
|
||||||
base: {
|
|
||||||
background: "var(--md-sys-color-surface-container-low)",
|
|
||||||
display: "flex",
|
|
||||||
minWidth: 0,
|
|
||||||
overflow: "hidden",
|
|
||||||
},
|
|
||||||
variants: {
|
|
||||||
sidebar: {
|
|
||||||
false: {
|
|
||||||
borderTopLeftRadius: "var(--borderRadius-lg)",
|
|
||||||
borderBottomLeftRadius: "var(--borderRadius-lg)",
|
|
||||||
overflow: "hidden",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 4: Start the dev server and verify the message area now fills the screen**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd packages/client && pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: The message area takes up the majority of the window. Sidebar stays on the left without overflowing.
|
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ../..
|
|
||||||
git add packages/client/src/Interface.tsx
|
|
||||||
git commit -m "fix: switch root layout to CSS Grid, message area fills remaining space"
|
|
||||||
git push origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 4: Fix Sidebar panel widths
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `packages/client/src/interface/Sidebar.tsx`
|
|
||||||
|
|
||||||
The Sidebar wraps both the server icon list and the channel list in a single `display: flex` div. We need to give each child an explicit width so they never squeeze or overflow.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Open `packages/client/src/interface/Sidebar.tsx` and find the root return statement**
|
|
||||||
|
|
||||||
It looks like:
|
|
||||||
```typescript
|
|
||||||
return (
|
|
||||||
<div style={{ display: "flex", "flex-shrink": 0 }}>
|
|
||||||
<ServerList ... />
|
|
||||||
<Show when={...}>
|
|
||||||
<Switch fallback={<Home />}>
|
|
||||||
<Match when={params.server}><Server /></Match>
|
|
||||||
</Switch>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Wrap ServerList in a fixed-width container (65px) and the channel sidebar in a 240px container**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
return (
|
|
||||||
<div style={{ display: "flex", height: "100%", "flex-shrink": 0 }}>
|
|
||||||
<div style={{ width: "65px", "flex-shrink": 0, overflow: "hidden" }}>
|
|
||||||
<ServerList
|
|
||||||
orderedServers={state.ordering.orderedServers(client())}
|
|
||||||
setServerOrder={state.ordering.setServerOrder}
|
|
||||||
unreadConversations={state.ordering
|
|
||||||
.orderedConversations(client())
|
|
||||||
.filter((channel) => channel.unread)}
|
|
||||||
user={user()!}
|
|
||||||
selectedServer={() => params.server}
|
|
||||||
onCreateOrJoinServer={() =>
|
|
||||||
openModal({ type: "create_or_join_server", client: client() })
|
|
||||||
}
|
|
||||||
menuGenerator={props.menuGenerator}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Show
|
|
||||||
when={
|
|
||||||
state.layout.getSectionState(LAYOUT_SECTIONS.PRIMARY_SIDEBAR, true) &&
|
|
||||||
!location.pathname.startsWith("/discover")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div style={{ width: "240px", "flex-shrink": 0, overflow: "hidden" }}>
|
|
||||||
<Switch fallback={<Home />}>
|
|
||||||
<Match when={params.server}>
|
|
||||||
<Server />
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Check in dev server — server icon column should be narrow (65px), channel list should be 240px, message area fills the rest**
|
|
||||||
|
|
||||||
Expected: Three visually distinct columns. The message area is clearly the widest.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add packages/client/src/interface/Sidebar.tsx
|
|
||||||
git commit -m "fix: explicit 65px server list and 240px channel sidebar widths"
|
|
||||||
git push origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 5: Set up Forgejo CI — build and deploy
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `.forgejo/workflows/deploy.yml`
|
|
||||||
|
|
||||||
This workflow runs on every push to `main`, builds the client, and rsyncs the output to the VPS directory that Caddy serves at `mithraic.space/app`.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Add an SSH deploy key to Forgejo**
|
|
||||||
|
|
||||||
On the VPS, generate a deploy key (if one doesn't exist):
|
|
||||||
```bash
|
|
||||||
ssh-keygen -t ed25519 -C "sanctum-web-deploy" -f ~/.ssh/sanctum_web_deploy -N ""
|
|
||||||
cat ~/.ssh/sanctum_web_deploy.pub >> ~/.ssh/authorized_keys
|
|
||||||
cat ~/.ssh/sanctum_web_deploy # copy this — it's the private key for the secret
|
|
||||||
```
|
|
||||||
|
|
||||||
In Forgejo → `sanctum-web` repo → Settings → Secrets, add:
|
|
||||||
- `DEPLOY_KEY` = the private key content from above
|
|
||||||
- `DEPLOY_HOST` = your VPS IP or hostname
|
|
||||||
- `DEPLOY_USER` = the SSH user on the VPS (e.g. `root` or `deploy`)
|
|
||||||
- `DEPLOY_PATH` = the path Caddy serves for mithraic.space/app (e.g. `/var/www/sanctum-web`)
|
|
||||||
|
|
||||||
- [ ] **Step 2: Create the workflow file**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p .forgejo/workflows
|
|
||||||
```
|
|
||||||
|
|
||||||
Create `.forgejo/workflows/deploy.yml`:
|
|
||||||
```yaml
|
|
||||||
name: Build & Deploy
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: docker
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '20'
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
run: npm install -g pnpm@10
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm --filter client build
|
|
||||||
|
|
||||||
- name: Deploy to VPS
|
|
||||||
env:
|
|
||||||
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
|
|
||||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
|
||||||
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
|
|
||||||
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
|
|
||||||
chmod 600 ~/.ssh/deploy_key
|
|
||||||
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
|
|
||||||
rsync -avz --delete \
|
|
||||||
-e "ssh -i ~/.ssh/deploy_key" \
|
|
||||||
packages/client/dist/ \
|
|
||||||
"$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/"
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit and push — watch the Actions tab on Forgejo**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add .forgejo/workflows/deploy.yml
|
|
||||||
git commit -m "ci: build and deploy to VPS on push to main"
|
|
||||||
git push origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: Forgejo Actions picks up the workflow, builds successfully, and rsyncs `dist/` to the VPS. Visit `mithraic.space/app` — the new layout should be live.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 6: Verify the live deployment
|
|
||||||
|
|
||||||
- [ ] **Step 1: Open `mithraic.space/app` in a browser**
|
|
||||||
|
|
||||||
Check:
|
|
||||||
- Server icon column is narrow on the left
|
|
||||||
- Channel list is a fixed-width column next to it
|
|
||||||
- Message area fills the remaining screen width
|
|
||||||
- No horizontal overflow or clipped panels
|
|
||||||
|
|
||||||
- [ ] **Step 2: Open Sanctum desktop app**
|
|
||||||
|
|
||||||
Since it loads `mithraic.space/app`, it should automatically show the new layout. Verify the same proportions hold in the desktop window.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Test on a narrow browser window (900px wide)**
|
|
||||||
|
|
||||||
Expected: The layout still shows the message area without horizontal scrollbar. If the member list appears (in a text channel), it can collapse or overlap at this width — that's acceptable for v1.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Tag the first release**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git tag v1.0.0
|
|
||||||
git push origin v1.0.0
|
|
||||||
```
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
# sanctum-web Layout Redesign
|
|
||||||
|
|
||||||
**Date:** 2026-04-22
|
|
||||||
**Repo:** git.mithraic.cloud/ad3laid3/sanctum-web
|
|
||||||
**Base:** stoatchat/for-web (Solid.js + Vite + TypeScript)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
|
|
||||||
Fork the Stoat web frontend and fix its layout proportions so the app feels native on desktop and works properly as a PWA. The message area should dominate the screen. Nothing in the API, auth, or business logic layer is changed.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Layout
|
|
||||||
|
|
||||||
The root shell is rebuilt as a four-column CSS Grid:
|
|
||||||
|
|
||||||
```
|
|
||||||
[server icons 65px] [channels 240px] [messages 1fr] [members 240px]
|
|
||||||
```
|
|
||||||
|
|
||||||
| Panel | Width | Behaviour |
|
|
||||||
|---|---|---|
|
|
||||||
| Server icon sidebar | 65px fixed | Icon-only, scrollable, always visible |
|
|
||||||
| Channel list | 240px default | User-resizable, collapsible |
|
|
||||||
| Message area | fills remaining space (1fr) | Message list + pinned input at bottom |
|
|
||||||
| Member list | 240px | Collapsible, hidden by default on narrow viewports |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
**Changed:**
|
|
||||||
- Root layout shell component — CSS replaced with proper grid
|
|
||||||
- Global CSS spacing/sizing tokens that cause proportion issues
|
|
||||||
|
|
||||||
**Not changed:**
|
|
||||||
- API client
|
|
||||||
- WebSocket layer
|
|
||||||
- Auth flow
|
|
||||||
- Message rendering components
|
|
||||||
- Channel/server/member components
|
|
||||||
- Any business logic
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Build & Deployment
|
|
||||||
|
|
||||||
- Repo: `git.mithraic.cloud/ad3laid3/sanctum-web`
|
|
||||||
- CI: Forgejo Actions — `pnpm build` on push to `main`, rsync `dist/` to VPS
|
|
||||||
- Serves at: `mithraic.space/app` (replaces current for-web output, no Caddy changes)
|
|
||||||
- Sanctum desktop app picks up the new frontend automatically on next launch
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Out of Scope
|
|
||||||
|
|
||||||
- Tauri desktop wrapper (separate project, builds on this frontend)
|
|
||||||
- Feature additions beyond layout fixes
|
|
||||||
- Mobile/responsive breakpoints beyond basic collapse behaviour
|
|
||||||
- Rebranding (keep existing Stoat/Revolt dark aesthetic)
|
|
||||||
|
|
@ -10,8 +10,6 @@ import { getThemeFiles, getActiveTheme, applyTheme, cycleTheme, reloadTheme, ope
|
||||||
|
|
||||||
// internal tray state
|
// internal tray state
|
||||||
let tray: Tray = null;
|
let tray: Tray = null;
|
||||||
// Keep strong reference to prevent garbage collection on Linux
|
|
||||||
let trayIcon: Electron.NativeImage;
|
|
||||||
|
|
||||||
// Create and resize tray icon for macOS
|
// Create and resize tray icon for macOS
|
||||||
function createTrayIcon() {
|
function createTrayIcon() {
|
||||||
|
|
@ -19,11 +17,10 @@ function createTrayIcon() {
|
||||||
const image = nativeImage.createFromDataURL(macOsTrayIconAsset);
|
const image = nativeImage.createFromDataURL(macOsTrayIconAsset);
|
||||||
const resized = image.resize({ width: 20, height: 20 });
|
const resized = image.resize({ width: 20, height: 20 });
|
||||||
resized.setTemplateImage(true);
|
resized.setTemplateImage(true);
|
||||||
trayIcon = resized;
|
return resized;
|
||||||
} else {
|
} else {
|
||||||
trayIcon = nativeImage.createFromDataURL(trayIconAsset);
|
return nativeImage.createFromDataURL(trayIconAsset);
|
||||||
}
|
}
|
||||||
return trayIcon;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initTray() {
|
export function initTray() {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue