sanctum/docs/superpowers/plans/2026-04-22-sanctum-web-layout-redesign.md

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.tsx and 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 Layout base 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 Content base 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.tsx and 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. 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

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/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
git tag v1.0.0
git push origin v1.0.0