---
title: Ollert (a full kanban tool built into the app)
---

# Ollert (native port)

## What it is

A full-featured kanban project tool — workspaces, boards, lists, cards, drag-and-drop,
labels, members, checklists, comments, due dates, and the alternate board views —
brought into Omniscio as a **native sidebar feature**. It is a
React port of the standalone `trello-web` frontend, not an embedded web page.

> **Status: Phases 1–5 integration-hardened.** The full app is
> mounted — sidebar entry, panel shell, boards/cards, card depth, alternate views,
> and realtime all ride the same hardened foundation (scoped CSS + portals, CSP for
> the API/images/socket, in-memory router, React-18-safe, error-contained). **Auth
> unified with Omniscio's Global Auth (Firebase) 2026-07-16** — the plugin's own login +
> refresh-token persistence were removed. Off by default. Remaining: dark-mode
> reconciliation + help-site copy (Phase 6), and the backend deploy (incl. Firebase
> storage for attachment uploads).
> Engineering detail + invariants live in the feature contract
> (`.claude/memory/contracts/ollert-integration-contract.md`).

## Where to find it

### What the user sees

- A **"Ollert"** row appears in the Omniscio sidebar (under the Omniscio built-ins group, in
  its alphabetical slot as "Ollert"), with the Ollert
  brand icon. The row only shows when the feature is enabled.
- Clicking it opens Ollert with a **left navigation sidebar** (Boards, Templates,
  Home, and your Workspaces — each expanding to its Boards / Members / Settings)
  beside the board panel — the same shape as Omniscio's other integrations, not a
  full-workspace takeover. The sidebar is resizable and theme-aware; signed out it
  shows a "sign in to Omniscio" strip. (No separate sessions list — Ollert hosts no
  Claude sessions of its own.)
- There is **no separate Ollert sign-in**. Ollert uses **Omniscio's own sign-in**
  (Global Auth) — sign in to Omniscio once and Ollert is authenticated automatically.
  When you are not signed in to Omniscio, Ollert shows a "Sign in to Omniscio to access your
  boards" prompt instead of the app.

## How it behaves

### How to turn it on

Ollert is gated on your `ollertEnabled` flag (default off; set by the 2026-05-23
data-presence migration for existing installs). There is **no Settings toggle**
anymore — the old "Enable Ollert" control, its nav row, and its search entry were
removed in the 2026-08-14 settings declutter (decision 2B). Settings → Ollert is
now a read-only landing reachable only by a direct deep link (`?section=ollert`).
While `ollertEnabled` is off, the sidebar row is hidden and nothing Ollert-related
loads.

Ollert talks to a **separately-hosted Ollert backend** (it is _not_ run by Omniscio).
The backend URL is **hardcoded** to `https://amcback.jls.dev/trello` (the unified
`amc-back` server) in `src/plugins/ollert/web/lib/runtime-config.ts` — it's a fixed
deploy target, not a setting. To point at a different backend (e.g. a local dev
instance at `http://localhost:8080/trello`), set the `VITE_API_URL` env var. The
realtime WebSocket URL is derived from it (`https://…` → `wss://…`).

### Sharing boards and cards (deep links)

Every card's ⋯ menu has a **"Copy link to card"** action. The board header has a
**"Copy link to board"** action. Both copy an `omniscio://` deep link to the
clipboard — not a regular web URL.

**What the link looks like:**

- Card: `omniscio://trello/card/<boardId>/<cardId>`
- Board: `omniscio://trello/board/<boardId>`

**How it works for the recipient:** anyone who has Omniscio installed and access to
that board can paste the link into their browser address bar or double-click it
from a document, and Omniscio will open directly to that card or board. No
copy-pasting of board names or searching required.

**Implementation note (for agents):** the link is built by
`DEEP_LINK_OLLERT_BUILD` IPC handler (`src/main/services/deep-link.ts`,
`ollertCardDeepLink` / `ollertBoardDeepLink` builders). The renderer calls it
via the `window.__AMC_OLLERT_LINKS__` bridge installed in `OllertPanel.tsx`, then
writes the result to the clipboard. The router that opens Omniscio to the right card or
board on receipt is in `src/renderer/src/lib/deep-link-router.ts`. Build,
parse, and route logic are unit-tested in
`tests/unit/deep-link-ollert-invite.test.ts` and
`tests/unit/lib/deep-link-router-ollert-invite.test.ts`.

### Recent additions (2026-06-17)

**Promote members to admin.** On the workspace members page (and the workspace
overview's members dialog), a workspace admin sees an **Admin / Member** dropdown
on each member row and can promote or demote anyone except the workspace owner
(whose role is fixed). Backed by `PATCH /workspaces/:id/members/:userId`.

**Custom colours everywhere.** Card labels, card covers, list tints, and board
backgrounds now offer a **custom colour picker** as the first option, followed by
the default swatches (the swatch sets were also expanded). Any colour is allowed —
the backend validates a well-formed hex rather than checking a fixed palette.

**Clickable links in comments.** Card comments use a small rich-text editor
(bold, italic, bullet/numbered lists, and an insert-link button). Typed URLs also
become clickable automatically. Comment content is sanitized on both save and
display, so links open safely in a new tab.

**Filter shows all members.** On a board that is visible to the whole workspace
(not private), the member filter — and the assign-to-card picker — now list every
workspace member, not just those explicitly added to the board. Assigning a
workspace member to a card adds them to the board automatically.

**Pending invitations show on the members page.** The workspace members page now
lists pending (invited-but-not-yet-joined) members in a "Pending invitations"
section below the roster, with a revoke action — previously they appeared only
inside the invite popup. The section is admin-only and reuses `InvitationsPanel`
(rendered with its invite form hidden); the invite form still lives in the
"Invite Workspace members" dialog.

**Done lists + unified completion.** A card has one "done" state — the
`completed` flag. The hover-reveal circle on the card front (board, table,
calendar) and the **"Mark as complete"** checkbox in the due-date popover now
drive the _same_ flag (the date pill shows "· Done" whenever the card is
complete); the legacy `dueComplete` column is mirrored to it on the server for
the overdue index. A list's **⋯ List actions** menu has a **"Mark as done
list"** toggle — any number of lists per board may be done lists, shown with a
green **Done** badge in the column header. Dragging a card **into** a done list
auto-marks it complete; dragging it back **out** into a normal list
un-completes it (reordering within the list never changes completion). Backend:
`is_done_list` on the `lists` table (migration 0020); the rule lives in
`cards.service` `moveCard` and the mirror in `updateCard`.

**Custom fields in the card's "Add" menu.** A card's **Add** menu now has a
**Custom Fields** item that opens an inline picker listing the board's custom
fields with a typed editor for each — so fields you create are reachable where you
go to add things to a card. The fields also still appear in the card's own Custom
Fields section; both edit the same values.

## For agents

### How it is built (for agents)

The vendored Ollert source lives in **`src/plugins/ollert/`** and is deliberately
isolated from Omniscio's tooling (its own ESLint/Prettier ignores, its own
`tsconfig.json` via `npm run typecheck:ollert`, and its own scoped Tailwind build).
Omniscio owns only the thin integration glue.

Key decisions and the four port challenges:

1. **Routing.** Ollert uses URL routes (React Router). Inside Omniscio those run in an
   **in-memory router** (`MemoryRouter`) scoped to the panel, so all ~208 Ollert
   components keep their `useParams` / `useNavigate` calls unchanged. Omniscio's renderer
   still owns the real browser URL. The composition root is
   `src/plugins/ollert/web/amc/AmcOllertApp.tsx` (replaces the standalone
   `web/main.tsx`).
2. **Styling collision.** Ollert and Omniscio both define tokens like `surface` and
   `accent`. Ollert gets its **own scoped Tailwind build** — every utility is
   namespaced under a `.ollert-scope` wrapper (`important: '.ollert-scope'`,
   preflight off), and its palette lives on `.ollert-scope` / `.dark .ollert-scope`
   as RGB-channel CSS vars. The prebuilt, committed `ollert.generated.css` is
   imported by the panel; regenerate it with `npm run build:ollert-css`.
3. **Shared types.** Ollert imports framework-free types from `@ollert/shared`,
   which is vendored under `src/plugins/ollert/shared/`. The API client is loosely
   typed on purpose (Eden Treaty `treaty<any>`) to stay decoupled from the
   un-vendored backend — but that looseness stays **inside the transport**. The
   plugin's public domain types (`WorkspaceSummary`, `BoardSummary`, `BoardSnapshot`,
   `SnapshotCard`, `BoardStats`, …) are declared by hand in
   `src/plugins/ollert/web/lib/api-types.ts`, and each feature's `api.ts` derives its
   exported aliases from those rather than from `typeof api`. Without that split the
   `any` propagated outward and every one of those public types *was* `any`, so a
   field rename on the wire compiled clean at every consumer; now it is a type error.
   Note `treaty<any>` is still exported, so a *direct* transport call site stays
   loose — the guarantee covers the plugin's public surface, not its call sites.
4. **Auth.** Ollert is **unified with Omniscio's Global Auth (Firebase)** — there is no
   separate Ollert login. `OllertPanel` installs a `window.__AMC_OLLERT_SESSION__`
   bridge whose `getToken()` fetches a Firebase ID token from Omniscio's main process over
   IPC (`ollert:get-firebase-token`); the API client attaches it as `Bearer` auth per
   request and retries once on a `401` with a freshly fetched token (the main process
   auto-refreshes tokens near their 1-hour expiry — no plugin-side refresh-token
   persistence). The panel gates on Omniscio sign-in (a "Sign in to Omniscio" prompt, zero
   network, when signed out); on sign-in it calls `GET /auth/me`, which auto-creates/
   links the backend account. The old email/password login/register/forgot UI and the
   encrypted `trelloRefreshToken` refresh persistence were removed in this unification.

**Theme.** Omniscio owns the `.dark` class on `<html>`; the scoped `.dark .ollert-scope`
tokens follow it. Omniscio's panel feeds Ollert's `ThemeContext` a value derived from
Omniscio's theme store rather than mounting Ollert's own provider (which would fight
Omniscio's identical `<html>.dark` mechanism). Full dark-mode reconciliation is a later
polish step.

**Network exception.** Unlike the rest of Omniscio (where the renderer never makes
direct network calls), the Ollert panel calls its API **directly from the
renderer**. This is the user's own Ollert session to a separate service — not a
paid Omniscio credential — and Omniscio's CSP already permits the external `https://` +
`wss://` calls. The only credential that touches Main is Omniscio's Firebase ID token,
minted per request and never persisted by the plugin.

### Where it is wired in Omniscio

- Sidebar registration: `src/shared/integration-registry.ts` (`id: 'trello'`,
  `featureFlag: 'trelloEnabled'`) + `src/renderer/src/integrations/ui-registry.ts`
  (icon + lazy panel + lazy `OllertSidebar` as `sidebarComponent`,
  `mobilePrefersPanel: true`; **no** `panelOwnsLayout` — the linear-board shape).
- Virtual-project sentinel: `OLLERT_PROJECT_ID = '__trello__'` in
  `src/shared/virtual-project-ids.ts` (also in `VIRTUAL_FOLDER_PATHS`).
- Sidebar hide-when-off gate: `SIDEBAR_RENDER_GATES` in
  `src/renderer/src/stores/project-visibility.ts`.
- Panel host (Omniscio glue): `src/renderer/src/features/ollert/OllertPanel.tsx`.
- Pane-2 nav sidebar (Omniscio glue): `src/renderer/src/features/ollert/OllertSidebar.tsx` —
  drives the panel via the vendored `navigateTo` bridge, reflects its route + sign-in
  status from `src/plugins/ollert/web/lib/ollert-ui-bridge.ts` (published by
  `UiStateBridge` in the composition root), and reuses the vendored `useWorkspaces`
  through the singleton `queryClient` (shared cache). The vendored `DashboardShell`
  hides its OWN internal sidebar when Omniscio-hosted (`isNativeSession()`).
- Settings: `sections/ollert/OllertSettings.tsx` — a read-only deep-link landing
  (no nav row, no toggle, no search entry), reachable only by `?section=ollert`.

## Related

- [mission-control.md](mission-control.md) — the built-in board app, which is a different product in the same sidebar.
- [notion-board.md](notion-board.md) — the board integration pointed at a Notion database.
- [deep-links.md](deep-links.md) — the `omniscio://` links the board's copy-link actions produce.

