Project management with tree view of all ticket relations.
  • TypeScript 80.5%
  • Svelte 16.8%
  • Go 2.1%
  • CSS 0.2%
  • Makefile 0.2%
  • Other 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Marcel M. Otte 30a236ab32
All checks were successful
CI / go (push) Successful in 51s
CI / test (push) Successful in 3m36s
CI / build (push) Successful in 39s
CI / desktop (push) Successful in 3m49s
fix(canvas): chrome clears the edges that are occupied (#332)
The pinned-highlight chip rendered behind Pro's time rail, and — reported in the
same breath — the encoding legend has been hidden by every left sidebar: it sits
at `left: 12px; z-index: 6` against a dock at `left: 44px; z-index: 20`.

Not one unlucky corner. Floating chrome and floating panels shared a coordinate
space with no agreement about who owned which region, so each picked absolute
coordinates and a z-index alone and they collided. **There is no corner that
stays free**, so moving either one only defers it.

`stores/canvas-insets.ts` is a signal channel named for its effect: FOSS owns it
and each thing occupying an edge is one producer — the left dock, the right
inspector (already a store in `panels.ts`, reused rather than copied), and the
bottom rail. `canvasInsets` says where the free area starts; the legend and the
chip offset themselves by it.

The pattern already existed, applied exactly once: `TimeRail` takes `leftInset`
and `rightInset` props, which is why the rail never collides with anything. This
makes it shared, so the next piece of chrome inherits the answer.

Measured, not inferred from which panel is open: panels differ in width, and a
hardcoded number is a second source of truth that drifts.

Five mutations verified on the channel, three on the two consumers — including
that removing the legend's offset fails, which is the bug as reported.

Signed-off-by: Marcel M. Otte <marcel.otte@mmo.to>
2026-11-03 20:13:01 +00:00
.forgejo/workflows build(ci): one desktop artifact per platform, and a loud missing installer (#314) 2026-10-28 21:56:25 +00:00
.vscode Initial Commit - MVP 2026-03-26 11:43:38 +01:00
backend test(proxy): assert the default listen address is loopback (#308) 2026-10-28 20:44:28 +00:00
CLA chore: retire Codeberg mirror, repoint to juurakko org 2026-07-31 10:39:43 +02:00
desktop fix(proxy): the FOSS binaries get a caller gate and loopback by default (#307, #308, #311) 2026-10-28 20:32:18 +00:00
dev/ci-templates chore: retire Codeberg mirror, repoint to juurakko org 2026-07-31 10:39:43 +02:00
docs fix(forgejo): clear a due date with unset_due_date, which is the only way (#255) 2026-10-08 16:55:50 +02:00
docs-site chore: retire Codeberg mirror, repoint to juurakko org 2026-07-31 10:39:43 +02:00
e2e fix(e2e): two assertions tested mechanisms that no longer exist (#314) 2026-10-28 21:29:15 +00:00
shared/proxycore fix(proxy): carry a request body that is not JSON (#297) 2026-11-03 12:01:08 +00:00
src fix(canvas): chrome clears the edges that are occupied (#332) 2026-11-03 20:13:01 +00:00
static Replace app icon with layered-cards design (#86) 2026-04-20 18:34:30 +02:00
.gitignore feat(settings-ia): live Style panel + panel-manager shell (#172, #174) 2026-07-22 11:53:16 +02:00
.npmrc Initial Commit - MVP 2026-03-26 11:43:38 +01:00
CONTRIBUTING.md fix(forgejo): clear a due date with unset_due_date, which is the only way (#255) 2026-10-08 16:55:50 +02:00
LICENSE APGL License 2026-07-20 10:59:44 +02:00
Makefile build(make): detect the webkit ABI and an experimental Go toolchain (#312) 2026-10-28 21:09:12 +00:00
package-lock.json fix(ci): regenerate lockfiles after Juurakko rebrand 2026-07-10 06:55:49 +02:00
package.json chore(brand): rename package/build metadata to juurakko 2026-07-09 21:38:43 +02:00
playwright.config.ts Initial Commit - MVP 2026-03-26 11:43:38 +01:00
README.md build(make): detect the webkit ABI and an experimental Go toolchain (#312) 2026-10-28 21:09:12 +00:00
svelte.config.js Initial Commit - MVP 2026-03-26 11:43:38 +01:00
tsconfig.json chore: Wave 1 cleanup before structural refactors (#119) 2026-05-12 10:55:29 +02:00
vite.config.ts chore(brand): rename app UI strings and PWA manifest to Juurakko 2026-07-09 21:39:17 +02:00
vitest.config.ts Extract AppHeader with grouped buttons and snippet slots (#67) 2026-04-15 09:17:52 +02:00

Juurakko

A browser-first application that visualizes issues and tickets from project management tools on an interactive infinite canvas with tree connections.

Load issues from your PM tool, see parent/child and blocking relationships as a visual graph, and edit issues directly from the canvas.

About the name

Juurakko is Finnish for the root system of a tree — the whole spreading, interwoven mass of roots a tree grows from (also rootstock / rhizome). It comes from the Finnish juuri ("root") plus the collective suffix -kko. The name suits a tool that lays your project out as a tree: rarely a single "root of all evil" but many roots, with every issue tracing back through its links to them.

How to say it (stress the first syllable)

  • For humans: YOO-rah-ko — "YOO" long like moon, a rolled r if you can manage it, and hold the double k a beat longer (YOO-rahk-ko).
  • In phonetic notation (IPA): [ˈjuːrɑkːo]

Features

  • Interactive canvas — pan, zoom, collapse/expand subtrees, search and filter
  • Multiple PM tool plugins — Forgejo, GitHub, and more coming
  • Write-back — edit status, assignee, title, description, labels, and comments directly on cards
  • Link creation — create parent/child and blocking relationships between issues from the canvas
  • Filters — per-dimension chip filters with fade/hide modes and boundary visualization
  • Child progress — progress bars showing closed/total counts with optional recursive descendant counting
  • Saved views — persist and switch between canvas configurations
  • Appearance — light/dark/system themes with customizable colors
  • Desktop app — native desktop application via Wails for Linux and Windows — double-click and go, no terminal needed
  • Go backend — single binary with embedded frontend and CORS proxy, cross-compiled for Linux, Windows, and macOS
  • Offline-capable — runs entirely in the browser with IndexedDB caching; backend only needed for CORS proxy

Quick Start

Download the latest release for your platform from the Releases page:

  • Linux — juurakko-desktop-linux-amd64
  • Windows — juurakko-desktop-windows-amd64-installer.exe (installer) or .exe (portable)

Run the app, add a connection to your PM tool in settings, and load a project.

Development

npm install
npm run dev

Open http://localhost:5173, add a connection to your PM tool in settings, and load a project.

Build

Desktop app

Requires Wails CLI:

make desktop        # build for current platform
make desktop-debug  # build with devtools enabled
make release        # Linux + Windows desktop apps + server binaries
make wails-config   # say which webkit ABI and Go toolchain the build will use

Two things are detected rather than asked of you, because both fail with errors that name the wrong culprit:

  • webkit2gtk 4.1 is used when present. The old 4.0 ABI is deprecated, and a leftover 4.0 package on a current distro links a stale libjxl and dies at the link step with undefined reference to 'JxlDecoderCreate@JXL_0'. Force the old ABI with make desktop WAILS_TAGS=webkit2_40 if you really need it.
  • An experimental Go toolchain — one whose version carries an -X: suffix, e.g. go1.26.5-X:nodwarf5 — is swapped for the stock build of the same version. Experimental toolchains emit export data the x/tools pinned by Wails cannot read, and the build fails with internal error: package "context" without types, which does not mention the toolchain. A stock go is left exactly as it is. Override with make desktop WAILS_GOTOOLCHAIN=go1.25.0, or =local to force your default and see the raw error.

make wails-config prints both decisions, so a failure can be diagnosed without reading the Makefile.

Frontend only (static SPA)

npm run build

Output in build/ — serve with any static file server.

Server binary (Go backend)

make backend      # current platform

Output in dist/. The binary embeds the frontend and serves it alongside a CORS proxy.

It listens on 127.0.0.1:8080 by default. The proxy forwards requests on behalf of the page it serves, so binding every interface would make it reachable by anything on the network — that has to be asked for explicitly:

./juurakko                      # loopback only (default)
./juurakko --addr :8080         # every interface — needed in a container
./juurakko --allow-origin http://localhost:5173   # when a dev server serves the page

In a container, --addr :8080 is required: a process bound to 127.0.0.1 inside a container cannot be reached through a published port.

Each run mints a proxy token, stamps it into the HTML it serves, and refuses any /api/proxy request that does not present it. Nothing to configure — but it does mean a page this binary did not serve cannot use the proxy.

Tests

make test-unit    # vitest (unit + component tests)
make test-e2e     # playwright (end-to-end tests)
make test         # both

Plugin Architecture

PM tool integrations are implemented as plugins conforming to the PMPlugin interface in src/lib/plugins/interface.ts. Each plugin handles authentication, issue fetching, write operations, and link management for its specific PM tool.

Plugin Status
Forgejo Included
GitHub Included
GitLab Soon(tm)
Taiga Soon(tm)
OpenProject Soon(tm)
Redmine Soon(tm)

Tech Stack

Contributing

Contributions are welcome! Juurakko is open core, so contributing requires a DCO sign-off and a one-time Contributor License Agreement (CLA) acceptance — both are quick. See CONTRIBUTING.md and CLA/icla.md for details.

License

Juurakko (this FOSS core) is licensed under the GNU Affero General Public License v3.0 only (AGPL-3.0-only) — see LICENSE.

Juurakko is open core: the commercial editions (Juurakko Pro, Juurakko Enterprise) build on this core under separate commercial terms. Contributions are accepted under the CLA (see Contributing), which is what allows them to be included in those editions while contributors keep their copyright.