11 KiB
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
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
git remote add upstream https://github.com/stoatchat/for-web
git fetch upstream
- Step 3: Install dependencies
pnpm install
- Step 4: Confirm dev server starts
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)
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
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
cd packages/client
pnpm build
Expected: dist/ folder is created with no errors.
- Step 3: Commit
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.tsxand find the two styled components at the bottom of the file
They look like this:
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
Layoutbase styles — switch from flex to grid
Change the Layout styled component's base to:
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
Contentbase styles — remove width:100%, add minWidth:0
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
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
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.tsxand find the root return statement
It looks like:
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
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
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):
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.rootordeploy) -
DEPLOY_PATH= the path Caddy serves for mithraic.space/app (e.g./var/www/sanctum-web) -
Step 2: Create the workflow file
mkdir -p .forgejo/workflows
Create .forgejo/workflows/deploy.yml:
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
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/appin 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
git tag v1.0.0
git push origin v1.0.0