Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
push:
branches:
- main
- develop

permissions:
contents: read
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ cover/
local_settings.py
db.sqlite3
db.sqlite3-journal
celerybeat-schedule*

# Flask stuff:
instance/
Expand Down
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,41 @@ and Hoard adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

## [0.2.0] - 2026-10-23

### Added

- Read-only public DiceCloud character-sheet links, live sheet projection with a
24-hour outage fallback, and a direct link back to each connected sheet.
- DiceCloud-backed profile summaries for identity, XP progress, currency, combat
statistics, abilities, saving throws, skills, and temporary hit points.

### Changed

- Refresh active linked DiceCloud profiles every 10 seconds while a campaign has
a connected participant, publishing updates only when the projected sheet data
changes and pausing sync after the existing presence window expires.
- Replaced local character sheets with lightweight player-character records that
retain campaign context, legacy name and HP fallback values, DiceCloud link, and
activity state.
- Reworked the profile and Party Rail around live DiceCloud data, including
MeterGroup HP displays and percentage-only health for hidden enemy HP.
- Changed XP sharing to campaign-level party XP, with a configurable number of
shares so players without a connected sheet can still receive a portion.
- Derived party currency from active connected DiceCloud sheets rather than local
money ledgers.
- Preserved generic encounter enemies and their local HP controls while making
connected player HP read-only.

### Removed

- Local NPC profiles, editable local character-sheet state, portrait uploads,
personal money ledgers, individual XP accounts, and local HP/rest mutations.
- Compendium pages, routes, and supporting data.
- The upgrade migration permanently removes existing local NPC, portrait, money,
individual-XP, health, and character-sheet data. Back up a `0.1.x` database
before deploying `0.2.0` if any of that information must be retained.

## [0.1.2] - 2026-10-07

### Changed
Expand Down
124 changes: 124 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Contributing to Hoard

Thank you for contributing to Hoard. This guide covers the local environment,
routine checks, release-version workflow, deployment, and architecture.

## Local development

Running `nix-shell` provides Python 3.14, uv, Node.js, npm, Git, and Docker Compose.
Docker Compose is the supported way to run the full stack; the shell is useful for
repository checks and maintenance.

Start the development environment with Daphne, Vite live reload, PostgreSQL, and
Redis:

```sh
docker compose -f compose.dev.yml up --build
```

Open `http://localhost:5173`. Vite proxies HTTP uploads, media, the release manifest,
and WebSockets to Daphne. Django is also available directly at
`http://localhost:8000`.

The application container applies migrations when it starts. Stop the stack without
removing its database or uploaded portraits with:

```sh
docker compose -f compose.dev.yml down
```

Add `--volumes` only when intentionally recreating the unsupported pre-release
database and all local uploads.

## Verification

Run these checks from a second terminal while the development stack is running:

```sh
docker compose -f compose.dev.yml exec app uv run pytest --reuse-db
docker compose -f compose.dev.yml exec app uv run ruff check hoard
docker compose -f compose.dev.yml exec frontend npm test
docker compose -f compose.dev.yml exec frontend npm run build
```

## Release versions

Use the project command to update the frontend and Python version declarations
together:

```sh
npm run version:bump -- patch
```

Replace `patch` with `minor` or `major` as appropriate. The command refuses to run
when existing version declarations do not agree.

## Production deployment

Production runs Daphne, Celery, PostgreSQL, Redis, and Nginx. Nginx serves static
assets and uploaded media directly. Traefik routes application HTTP and WebSocket
traffic straight to Daphne, while sending `/static/` and `/media/` requests to
Nginx.

Create the external network and local production environment file once:

```sh
docker network create web
cp .env.example .env
sudo install -d -o 1000 -g 1000 -m 0755 \
/data/appdata/hoard/media \
/data/appdata/hoard/static
```

Set a long random `DJANGO_SECRET_KEY` and a strong `POSTGRES_PASSWORD` in `.env`,
then build and start the deployment for `https://dnd.turnr.net`:

```sh
docker compose up -d --build
```

Compose loads `.env` automatically. Database names and users can be changed with
`POSTGRES_DB` and `POSTGRES_USER`. Persistent production data is stored below
`/data/appdata/hoard`. The application image runs as UID/GID `1000`, so its media
and static directories must be writable by that account before the first start.

To inspect status and logs:

```sh
docker compose ps
docker compose logs -f app worker nginx
```

### Periodic tasks

Celery Beat runs the static DiceCloud-profile refresh every 10 seconds. Its local
`celerybeat-schedule` file records when tasks last ran, but Hoard does not need to
retain that state between container restarts: a restart may delay one refresh, and
the next scheduled refresh restores normal operation. The entrypoint therefore
stores it in `/tmp`, keeping generated scheduler files out of the repository and
away from persistent application data.

If Hoard later needs user-managed or durable periodic schedules, use a shared
database-backed scheduler such as `django-celery-beat` rather than persisting the
container-local schedule file.

## Architecture

- Django owns authentication, campaign data, portrait uploads, and the initial SPA
response. Nginx serves uploaded media directly in production.
- Application queries and commands use authenticated WebSockets. HTTP is limited to
session/CSRF operations, portrait uploads, static/media delivery, and the release
manifest.
- Redis carries campaign broadcasts, cache data, and Celery messages.
- PostgreSQL stores the campaign, audit, money, and XP ledgers.
- Vue and PrimeVue provide the browser application; Vite builds its production
assets into `hoard/dist`.

See [the API guide](docs/api.md) and [the initial release brief](docs/initial-release.md)
for the protocol and release scope.

## AI policy

Listen here, I'm a software dev, have been making Django apps since before you were
even a stain on your parents bedsheets. I'm taking assistance to speed things up,
and I don't care.
145 changes: 26 additions & 119 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
# Hoard

Hoard is a small campaign companion for shared XP, coin ledgers, calendar tracking,
character profiles, invitations, and campaign administration.
<p align="center">
<img src="docs/images/hoard-wordmark.png" alt="Hoard" width="600">
</p>

Hoard is a campaign companion for the everyday work of running a tabletop game.
It keeps the party's shared XP, calendar, roster, DiceCloud characters, and
campaign administration in one live, responsive workspace.

## Screenshots

### Desktop

The game-master dashboard keeps the party's progress, character details, and
campaign tools close at hand.

![Hoard game-master dashboard on desktop](docs/images/hoard-desktop.png)

### Mobile

The same character information and campaign navigation adapt for smaller screens.

<p align="center">
<img
src="docs/images/hoard-mobile.png"
Expand All @@ -21,125 +31,22 @@ character profiles, invitations, and campaign administration.

## Current release

Hoard `0.1.0` focuses on the shared campaign tools that are ready for regular use:
Hoard `0.2.0` focuses on the shared campaign tools ready for regular play. Its
shared-XP workflow reflects one table's deliberately unusual approach to advancement:
the party earns and divides XP together, rather than tracking awards per character.

- shared XP for active player characters, including level progress and immutable
ledger history;
- personal and campaign coin accounts with grants, spending, transfers, exact-value
exchanges, and reversals;
- a campaign calendar whose date is recorded against ledger and audit events;
- player-character and NPC profiles with portraits, free-text race and class, and
profile editing;
- shared XP, divided by the configured party size—including members without a
DiceCloud sheet—with level progress and an immutable party ledger;
- a campaign calendar that records its date alongside ledger and audit events;
- read-only DiceCloud character sheets, with player and GM connection management;
- campaign membership, shareable invitations, roster management, and GM controls;
- a responsive campaign shell with live WebSocket updates and desktop/mobile party
navigation.

Character profiles retain the full sheet layout as a preview of planned features.
Unavailable statistics, abilities, skills, inventory, equipment, and character-detail
sections are clearly marked **Coming soon** and use skeleton values rather than
placeholder character data. The Compendium is likewise visible in navigation but is
not part of this release.

## Development

To bump the release version consistently across the frontend and Python metadata:

```sh
npm run version:bump -- patch
```

Replace `patch` with `minor` or `major` as appropriate. The command refuses to run
if the existing version declarations do not agree.

Docker Compose is the supported full-stack development workflow. It starts Daphne,
Vite with live reload, PostgreSQL, and Redis with persistent local volumes:

```sh
docker compose -f compose.dev.yml up --build
```

Open `http://localhost:5173`. Vite proxies HTTP uploads, media, the release manifest,
and WebSockets to Daphne. Django is also available directly at
`http://localhost:8000`.

The application container applies migrations when it starts. To run checks in a
second terminal:

```sh
docker compose -f compose.dev.yml exec app uv run pytest --reuse-db
docker compose -f compose.dev.yml exec app uv run ruff check hoard
docker compose -f compose.dev.yml exec frontend npm test
docker compose -f compose.dev.yml exec frontend npm run build
```

Stop the stack without deleting its database or uploaded portraits:

```sh
docker compose -f compose.dev.yml down
```

Add `--volumes` only when intentionally recreating the unsupported pre-release
database and all local uploads.

## Production

Production runs Daphne, Celery, PostgreSQL, Redis, and Nginx. Nginx serves static
assets and uploaded media directly. Traefik routes application HTTP and WebSocket
traffic straight to Daphne, while sending `/static/` and `/media/` requests to
Nginx.

Create the external network and local production environment file once:

```sh
docker network create web
cp .env.example .env
sudo install -d -o 1000 -g 1000 -m 0755 \
/data/appdata/hoard/media \
/data/appdata/hoard/static
```

Set a long random `DJANGO_SECRET_KEY` and a strong `POSTGRES_PASSWORD` in `.env`,
then build and start the deployment for `https://dnd.turnr.net`:

```sh
docker compose up -d --build
```

Compose loads `.env` automatically. Database names and users can be changed with
`POSTGRES_DB` and `POSTGRES_USER`. Persistent production data is stored below
`/data/appdata/hoard`. The application image runs as UID/GID `1000`, so its media
and static directories must be writable by that account before the first start.

To inspect status and logs:

```sh
docker compose ps
docker compose logs -f app worker nginx
```

## Architecture

- Django owns authentication, campaign data, portrait uploads, and the initial SPA
response. Nginx serves uploaded media directly in production.
- Application queries and commands use authenticated WebSockets. HTTP is limited to
session/CSRF operations, portrait uploads, static/media delivery, and the release
manifest.
- Redis carries campaign broadcasts, cache data, and Celery messages.
- PostgreSQL stores the campaign, audit, money, and XP ledgers.
- Vue and PrimeVue provide the browser application; Vite builds its production
assets into `hoard/dist`.

See [the API guide](docs/api.md) and [the initial release brief](docs/initial-release.md)
for the protocol and release scope.

## Local tool shell
- live WebSocket updates in a responsive desktop and mobile interface.

Running `nix-shell` provides Python 3.14, uv, Node.js, npm, Git, and Docker Compose.
Docker Compose remains the supported way to run the full stack; the shell is useful
for repository checks and maintenance.
DiceCloud remains the source of truth for character-sheet data. Hoard fetches public,
read-only creature data live and uses a short-lived outage fallback without keeping a
local copy of a character sheet.

## AI policy
## Contributing

Listen here, I'm a software dev, have been making Django apps since before you were
even a stain on your parents bedsheets. I'm taking assistance to speed things up,
and I don't care.
See [CONTRIBUTING.md](CONTRIBUTING.md) for the local development environment,
verification commands, release-version workflow, deployment notes, and architecture.
36 changes: 36 additions & 0 deletions compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,42 @@ services:
- .:/app
- dev_media:/app/media

worker:
build: .
command: worker
environment: &app-environment
DJANGO_DEBUG: "true"
DJANGO_ALLOWED_HOSTS: localhost,127.0.0.1,app
POSTGRES_DB: hoard
POSTGRES_USER: hoard
POSTGRES_PASSWORD: hoard
POSTGRES_HOST: db
REDIS_URL: redis://redis:6379/0
REDIS_CACHE_URL: redis://redis:6379/1
CELERY_BROKER_URL: redis://redis:6379/2
CELERY_RESULT_BACKEND: redis://redis:6379/2
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
volumes:
- .:/app
- dev_media:/app/media

beat:
build: .
command: beat
environment: *app-environment
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
volumes:
- .:/app
- dev_media:/app/media

frontend:
image: node:22-bookworm-slim
working_dir: /app
Expand Down
Loading
Loading