No description
Find a file
2026-01-09 00:00:00 -06:00
.claude Add a color tag. 2025-12-09 00:00:00 -06:00
.roo Big rewrite to solidjs 2025-12-19 00:00:00 -06:00
cmd Fixes and closes #2 drag and drop card functionality not working 2026-01-09 00:00:00 -06:00
docs screenshot update 2025-12-21 00:00:00 -06:00
internal Cache fix for locale and theme change. 2025-12-23 00:00:00 -06:00
plans Big rewrite to solidjs 2025-12-19 00:00:00 -06:00
.dockerignore Major update to include a build pipeline for css and js. 2025-12-09 00:00:00 -06:00
.env.example Switch from local password to oauth2. 2025-12-11 00:00:00 -06:00
.gitignore Update. 2025-12-21 00:00:00 -06:00
AGENTS.md Caching! It reduces load times a lot! 2025-12-23 00:00:00 -06:00
CLAUDE.md Big rewrite to solidjs 2025-12-19 00:00:00 -06:00
docker-compose.yml Switch from local password to oauth2. 2025-12-11 00:00:00 -06:00
Dockerfile Style updates. 2025-12-19 00:00:00 -06:00
go.mod Switch from local password to oauth2. 2025-12-11 00:00:00 -06:00
go.sum Switch from local password to oauth2. 2025-12-11 00:00:00 -06:00
LICENSE License agreement change. 2025-12-19 00:00:00 -06:00
mise.toml Mise. 2026-01-09 00:00:00 -06:00
README.md Caching! It reduces load times a lot! 2025-12-23 00:00:00 -06:00

*Free for personal use, commercial SaaS rights reserved.


A self-hosted, minimalistic browser home/dashboard for links and notes with a beautiful Fizzy-inspired interface. Built to become your browser's home.

full screenshot


Features

  • Multiple Boards - Organize links and notes across boards for different contexts
  • Fizzy-inspired Interface - Draggable lists with horizontal and vertical drag-and-drop
  • Markdown Notes - Add markdown-formatted notes with custom color syntax
  • Auto Favicons - Automatically fetches and displays site favicons
  • Copy/Move Lists - Transfer lists between boards with all items intact
  • Mobile Responsive - Full feature access on mobile devices with touch optimization
  • Stealth UI - Minimal navigation that fades in when needed

Built for Performance

  • Lightweight - Go backend with SQLite, SolidJS front end.
  • Minimal Footprint - Docker image under 20MB
  • Instant Load - ~100ms full page load.
  • Fast - Less than 20kb with cache loaded, ~150kb including cache hydration.
  • In-Memory Caching - Server-side LRU cache for fully hydrated HTML, reducing database load and response times.

Prerequisites

⚠️ OAuth2 authentication is recommended for production.

Loom uses OAuth2/OIDC for authentication by default. However, it also supports a Standalone Mode for local usage where no external provider is required.


Quick Start (Standalone Mode)

Loom can be run in a "standalone" mode for local usage without needing an external OAuth2 provider.

  1. Run with Docker:

    docker run -d \
      --name loom \
      -p 8080:8080 \
      -v loom-data:/data \
      -e SESSION_KEY=$(openssl rand -hex 32) \
      -e ENCRYPTION_KEY=$(openssl rand -hex 32) \
      ghcr.io/crueber/loom:latest
    

    Note: By omitting OAUTH2_ISSUER_URL, Loom automatically enters standalone mode.

  2. Access the app at http://localhost:8080. You will be automatically logged in as user@standalone.


Quick Start (OAuth2 Mode)

🚀 Docker Compose (Recommended)

1. Configure OAuth2 Provider & Generate Keys

See the OAuth2 Provider Setup section below for detailed instructions on configuring Authentik or another OIDC provider.

Generate session keys:

openssl rand -hex 32  # SESSION_KEY
openssl rand -hex 32  # ENCRYPTION_KEY

2. Configure Environment

cp .env.example .env
# Edit .env with your OAuth2 credentials and session keys

Required variables in .env:

OAUTH2_ISSUER_URL=https://auth.example.com/application/o/loom/
OAUTH2_CLIENT_ID=your_client_id
OAUTH2_CLIENT_SECRET=your_client_secret
OAUTH2_REDIRECT_URL=http://localhost:8080/auth/callback
SESSION_KEY=your_64_char_hex_string
ENCRYPTION_KEY=your_64_char_hex_string

3. Start Loom

docker-compose up -d

4. Access & Login

Open http://localhost:8080 and click "Login using OAuth2". You'll be redirected to your OAuth2 provider, and upon successful login, your user account and default board will be created automatically.


🐳 Docker Run
# Create a volume for data persistence
docker volume create loom-data

# Run the container with environment variables
docker run -d \
  --name loom \
  -p 8080:8080 \
  -v loom-data:/data \
  -e OAUTH2_ISSUER_URL=https://auth.example.com/application/o/loom/ \
  -e OAUTH2_CLIENT_ID=your_client_id \
  -e OAUTH2_CLIENT_SECRET=your_client_secret \
  -e OAUTH2_REDIRECT_URL=http://localhost:8080/auth/callback \
  -e SESSION_KEY=your_64_char_hex_string \
  -e ENCRYPTION_KEY=your_64_char_hex_string \
  loom:latest


OAuth2 Provider Setup

🔐 Authentik Configuration

1. Create OAuth2/OIDC Application

  • Go to Authentik admin panel → ApplicationsProviders
  • Click "Create" → Select "OAuth2/OpenID Provider"

2. Configure Provider

  • Name: Loom
  • Authorization flow: Explicit consent or Implicit consent
  • Redirect URIs: http://localhost:8080/auth/callback (adjust for your domain)
  • Scopes: Ensure openid, profile, and email are included
  • ⚠️ ID Token encryption: DISABLE (leave ID token as signed JWT, not encrypted)

3. Save Credentials

  • Note the Client ID and Client Secret
  • Copy the OpenID Configuration Issuer URL
    • Example: https://auth.example.com/application/o/loom/
    • ⚠️ Do NOT include /.well-known/openid-configuration (Loom appends this automatically)

🔑 Other OIDC Providers (Keycloak, etc.)

Loom works with any OIDC-compliant provider. Ensure your provider:

  • Supports OpenID Connect Discovery (/.well-known/openid-configuration)
  • Includes openid, profile, and email scopes
  • Returns an email claim in the ID token
  • Uses signed JWT tokens (not encrypted JWE tokens)

Configure the redirect URI to: http://your-domain:8080/auth/callback



Configuration

⚙️ Environment Variables

Required

Variable Description Example
OAUTH2_ISSUER_URL OAuth2 provider issuer URL (without .well-known suffix) https://auth.example.com/application/o/loom/
OAUTH2_CLIENT_ID OAuth2 client ID from your provider abc123
OAUTH2_CLIENT_SECRET OAuth2 client secret from your provider secret123
OAUTH2_REDIRECT_URL OAuth2 callback URL (must match provider config) http://localhost:8080/auth/callback
SESSION_KEY 32-byte hex key for session signing (64 chars) Generate with openssl rand -hex 32
ENCRYPTION_KEY 32-byte hex key for cookie encryption (64 chars) Generate with openssl rand -hex 32

Optional

Variable Description Default
DATABASE_PATH Path to SQLite database file ./data/loom.db
PORT HTTP server port 8080
SESSION_MAX_AGE Session duration in seconds 31536000 (1 year)
SECURE_COOKIE Enable secure cookies (HTTPS only) false

See .env.example for a complete example configuration file.



User Management

🔄 Auto-Provisioning - Users are automatically created when they first log in via OAuth2. No manual user management is required.

  • Users are identified by their email address from the OAuth2 provider
  • On first login, a new user account is created automatically
  • A default board is created for each new user
  • Existing users (identified by email) will log in to their existing account

Usage Guide

📝 Creating Lists & Links

Creating Lists

  1. Click the "+ Add List" button on the right
  2. A new list card appears, flipped to show the configuration panel
  3. Enter a title (required)
  4. Optionally change the color:
    • Click one of the 8 preset color buttons
    • Or use the color picker for any custom hex color
    • Default: Blue (#3D6D95)
  5. Click "Save" or press ESC to cancel

Adding Links

  1. Click "+ Add Link" at the bottom of any list
  2. Enter a URL (required) and title (required)
  3. Click "Save" or press ESC to cancel
  4. Favicon fetched automatically

🎯 Organizing & Navigation

Drag & Drop

  • Drag lists horizontally by header to reorder (auto-scrolls near edges)
  • Drag links vertically within and between lists
  • Drag-to-scroll: Click and drag whitespace to scroll horizontally
  • Mobile: Long-press (200ms) to initiate drag

List Management

  • Click list header to collapse/expand
  • Gear icon (⚙️) to configure:
    • Edit title
    • Change color
    • Copy/move to other boards
    • Delete list

Link Management

  • Gear icon (⚙️) to configure:
    • Edit title and URL
    • Delete link

Keyboard Shortcuts

  • ESC - Close configuration panel
  • Enter - Save changes

💾 Import/Export

Export

  • Click "Export" to download your links as JSON
  • Backup your entire board structure

Import

  • Click "Import" and choose a JSON file
  • Merge mode: Adds new data, updates existing by ID
  • Replace mode: Deletes all data and imports fresh


Development

🛠️ Local Development Setup

Prerequisites

  • Go 1.23 or later
  • Node.js (for frontend bundling)
  • OAuth2 provider configured (see OAuth2 Provider Setup)

Build & Run

# Clone repository
git clone <repository-url>
cd loom

# Install Go dependencies
go mod download

# Build frontend bundle
cd cmd/server
node build.js
cd ../..

# Set environment variables
export OAUTH2_ISSUER_URL=https://auth.example.com/application/o/loom/
export OAUTH2_CLIENT_ID=your_client_id
export OAUTH2_CLIENT_SECRET=your_client_secret
export OAUTH2_REDIRECT_URL=http://localhost:8080/auth/callback
export SESSION_KEY=$(openssl rand -hex 32)
export ENCRYPTION_KEY=$(openssl rand -hex 32)

# Build and run
go build -o bin/server ./cmd/server
./bin/server

Access at http://localhost:8080


🏗️ Build for Production

Docker Build

docker build -t loom:latest .

The Dockerfile uses multi-stage builds:

  • Stage 1: golang:1.24-alpine builder (CGO disabled for static binaries)
  • Stage 2: scratch runtime (final image < 15MB)
  • Frontend assets embedded via //go:embed static

Binary Build

CGO_ENABLED=0 go build -o loom ./cmd/server


Security Considerations

🔒 Production Checklist

  • Use HTTPS (reverse proxy with nginx, Caddy, or Traefik)
  • Set SECURE_COOKIE=true when using HTTPS
  • Generate strong SESSION_KEY and ENCRYPTION_KEY (never reuse) in .env
  • Keep OAUTH2_CLIENT_SECRET secret (never commit to git)
  • Use HTTPS for OAuth2 redirect URL in production
  • Disable ID token encryption in OAuth2 provider
  • Enable rate limiting at reverse proxy level
  • Regularly backup SQLite database

Troubleshooting

OAuth2 Login Issues

"Failed to initialize OAuth2 client"

  • Check OAUTH2_ISSUER_URL is correct
  • Do NOT include /.well-known/openid-configuration in URL
  • Verify provider is accessible

"Invalid session state"

  • Check cookies are enabled
  • Loom uses SameSite=Lax for OAuth2 compatibility
  • Clear browser cookies and try again

"Failed to verify ID token"

  • ⚠️ Disable ID token encryption in OAuth2 provider
  • ID token must be signed JWT, not encrypted JWE
  • Check provider scopes include openid, profile, email

Redirect issues

  • Ensure OAUTH2_REDIRECT_URL matches provider configuration exactly
  • Check for trailing slashes

🔧 General Issues

Cannot access application

  • Check server is running: docker ps or docker logs loom
  • Verify port is free: lsof -i :8080
  • Ensure all required OAuth2 env vars are set

Auto-provisioning fails

  • Check logs for "Failed to provision user"
  • Verify OAuth2 provider sends email claim in ID token
  • Check database is writable

Favicons not loading

  • Requires outbound HTTPS to Google's favicon service
  • Some sites may not have favicons

Session expires too quickly

  • Check SESSION_MAX_AGE environment variable
  • Default: 31536000 (1 year)


Contributing

Suggestions and bug reports are welcome!

If you want to work on a new feature, it's preferable to submit the feature first and work through the details before sending a PR. Unsolicited PRs will most likely be just be closed.

Guiderails for submissions:

  • No new dependencies unless strictly necessary. The maintainer will let you know.
  • No backward incompatible changes without migrations.
  • Don't hardcode colors or icons, et al.
  • Provide screenshot of changes in PR.

Authored with ❤️ using Go and SolidJS by 👨crueber and 🤖AI.
...and maybe other contributors at some point?

⬆ Back to Top