Right-side panel drawer with pluggable content (first plugin: Forgejo issues) #31

Closed
opened 2026-08-26 11:10:09 +01:00 by pedromcaraujo · 0 comments

Why

Forge channels map 1:1 to repos, and Forgejo issues are per-repo, so a channel has an obvious "companion" view. Today that companion is a manual browser split. This adds a right-side drawer to the launcher whose content is a plugin, so issues are just the first of several things the drawer can host (embedded URL, command output, notes, … later).

The drawer (infrastructure)

  • web/index.html's <main> is today a single fullscreen <iframe id="frame">. Make it a flex row: terminal iframe + a right panel column.
  • Collapsed = a thin rail with an arrow on the right edge; expanded = a fixed (ideally resizable) column that stays put. Open/closed state persists in localStorage, mirroring the existing theme toggle.
  • Per-channel: a channel with no panel config shows no drawer arrow.
  • Mobile: on narrow screens the drawer overlays the terminal (slides over) rather than splitting width — Forge is deliberately phone-friendly.

Plugin contract

Each plugin is three parts:

  • Config schema (Nix)panel.type + type-specific fields, added to the channels attrset in modules/forge.nix.
  • Server handlers — a new forge-panel sidecar (copy the FastAPI pattern of web/upload.py / web/tmux_ctl.py, wired in modules/launcher.nix as a systemd service on a loopback port + nginx location). The sidecar dispatches by type under /panel/<channel>/…. The plugin holds the secret and does all backend calls server-side — tokens never reach the browser.
  • Client renderer — a component keyed by type, handed the panel data plus a channel context so it can call the existing /type endpoint (inject text into the active tmux session, exactly as the upload dropzone does) and its own server handlers.

Adding a future plugin = one config type + one server handler + one renderer.

First plugin: forgejo-issues

Exercises the whole contract (list read, lazy detail read, create write, inject action, external link).

  • Accordion list — rows of #number · title with state/label chips. Click a row to expand; click the issue number to inject look at issue #N: <title> into the active session.
  • Lazy detail on expand — fetch full detail on expand to keep the list light. Show the body + metadata + an "Open in Forgejo" link (html_url from the API). No comments (send people to Forgejo for the thread).
  • Markdown — the sidecar renders the body through Forgejo's /markdown render API and returns sanitized HTML; the frontend just injects it (no XSS surface, matches Forgejo's own rendering, restyled by Forge's theme).
  • Quick-add — a compact input at the top: title required, body optional (behind a small expander). POSTs a new issue, then refetches the list. Directly serves the repo's own rule of capturing tasks as issues.

Configuration (sketch)

channels.app = {
  cwd = "/home/me/code/app";
  panel = {
    type = "forgejo-issues";
    repo = "me/app";
    baseUrl = "https://git.example";   # or inherit a global default
    tokenFile = "/run/secrets/forge-forgejo-token";  # read by the sidecar
  };
};

Security

  • The API token lives in a file the sidecar reads (sops-nix or an env file à la sessionEnvFiles) — never a literal in the .nix (module values land in the world-readable /nix/store) and never sent to the browser.
  • Quick-add needs a write-scoped token (issue:write). A channel that should only watch can use a read-only token; the plugin must degrade gracefully (hide quick-add) when the token can't write.

Suggested build order

  1. Drawer layout + collapse/pin persistence + panel config plumbing (no data).
  2. forge-panel sidecar + forgejo-issues read path (accordion + lazy detail + markdown-API rendering + open-in-Forgejo link).
  3. Click-to-inject wiring to /type.
  4. Quick-add (write path) + graceful read-only degradation.
  5. README ## Options + ## Layout updates; mobile overlay pass.

Acceptance criteria

  • A channel with a panel config shows a right-edge arrow; clicking it expands a drawer that stays open across reloads (per-device).
  • The drawer lists the channel's open Forgejo issues as an accordion; expanding one shows body (Forgejo-rendered) + metadata + a link to Forgejo.
  • Clicking an issue injects a reference to it into the active terminal.
  • Quick-add creates an issue (title + body) and the list refreshes.
  • The Forgejo token is never exposed to the browser and is not baked into the Nix store.
  • On a phone the drawer overlays rather than crushing the terminal width.

Notes

This can be split into a "drawer + plugin contract" issue and a "forgejo-issues plugin" issue if the single issue gets unwieldy during implementation.

## Why Forge channels map 1:1 to repos, and Forgejo issues are per-repo, so a channel has an obvious "companion" view. Today that companion is a manual browser split. This adds a **right-side drawer** to the launcher whose content is a **plugin**, so issues are just the first of several things the drawer can host (embedded URL, command output, notes, … later). ## The drawer (infrastructure) - `web/index.html`'s `<main>` is today a single fullscreen `<iframe id="frame">`. Make it a flex row: terminal iframe + a right panel column. - **Collapsed** = a thin rail with an arrow on the right edge; **expanded** = a fixed (ideally resizable) column that stays put. Open/closed state persists in `localStorage`, mirroring the existing theme toggle. - **Per-channel**: a channel with no `panel` config shows no drawer arrow. - **Mobile**: on narrow screens the drawer **overlays** the terminal (slides over) rather than splitting width — Forge is deliberately phone-friendly. ## Plugin contract Each plugin is three parts: - **Config schema (Nix)** — `panel.type` + type-specific fields, added to the `channels` attrset in `modules/forge.nix`. - **Server handlers** — a new `forge-panel` sidecar (copy the FastAPI pattern of `web/upload.py` / `web/tmux_ctl.py`, wired in `modules/launcher.nix` as a systemd service on a loopback port + nginx `location`). The sidecar dispatches by `type` under `/panel/<channel>/…`. **The plugin holds the secret and does all backend calls server-side** — tokens never reach the browser. - **Client renderer** — a component keyed by `type`, handed the panel data plus a channel context so it can call the existing `/type` endpoint (inject text into the active tmux session, exactly as the upload dropzone does) and its own server handlers. Adding a future plugin = one config type + one server handler + one renderer. ## First plugin: `forgejo-issues` Exercises the whole contract (list read, lazy detail read, create write, inject action, external link). - **Accordion list** — rows of `#number · title` with state/label chips. Click a row to expand; click the issue **number** to inject `look at issue #N: <title>` into the active session. - **Lazy detail on expand** — fetch full detail on expand to keep the list light. Show the **body + metadata + an "Open in Forgejo" link** (`html_url` from the API). **No comments** (send people to Forgejo for the thread). - **Markdown** — the sidecar renders the body through Forgejo's **`/markdown` render API** and returns sanitized HTML; the frontend just injects it (no XSS surface, matches Forgejo's own rendering, restyled by Forge's theme). - **Quick-add** — a compact input at the top: **title required, body optional** (behind a small expander). `POST`s a new issue, then refetches the list. Directly serves the repo's own rule of capturing tasks as issues. ## Configuration (sketch) ```nix channels.app = { cwd = "/home/me/code/app"; panel = { type = "forgejo-issues"; repo = "me/app"; baseUrl = "https://git.example"; # or inherit a global default tokenFile = "/run/secrets/forge-forgejo-token"; # read by the sidecar }; }; ``` ## Security - The API token lives in a **file the sidecar reads** (sops-nix or an env file à la `sessionEnvFiles`) — **never a literal in the `.nix`** (module values land in the world-readable `/nix/store`) and **never sent to the browser**. - Quick-add needs a **write-scoped** token (`issue:write`). A channel that should only watch can use a read-only token; the plugin must degrade gracefully (hide quick-add) when the token can't write. ## Suggested build order 1. Drawer layout + collapse/pin persistence + `panel` config plumbing (no data). 2. `forge-panel` sidecar + `forgejo-issues` read path (accordion + lazy detail + markdown-API rendering + open-in-Forgejo link). 3. Click-to-inject wiring to `/type`. 4. Quick-add (write path) + graceful read-only degradation. 5. README `## Options` + `## Layout` updates; mobile overlay pass. ## Acceptance criteria - [ ] A channel with a `panel` config shows a right-edge arrow; clicking it expands a drawer that stays open across reloads (per-device). - [ ] The drawer lists the channel's open Forgejo issues as an accordion; expanding one shows body (Forgejo-rendered) + metadata + a link to Forgejo. - [ ] Clicking an issue injects a reference to it into the active terminal. - [ ] Quick-add creates an issue (title + body) and the list refreshes. - [ ] The Forgejo token is never exposed to the browser and is not baked into the Nix store. - [ ] On a phone the drawer overlays rather than crushing the terminal width. ## Notes This can be split into a "drawer + plugin contract" issue and a "forgejo-issues plugin" issue if the single issue gets unwieldy during implementation.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pedromcaraujo/forge#31
No description provided.