From 98ee2d975bf7176bb110a2c07b0b31574b403ad2 Mon Sep 17 00:00:00 2001 From: Chinmay Chaudhari Date: Wed, 1 Jul 2026 20:20:36 +0530 Subject: [PATCH 1/2] feat(website): project website with nightly build tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a self-contained Hugo site and the CI to publish it (Deliverable 5). Site (website/) - Themeless Hugo site (all layouts in-repo, no submodule/theme fetch): landing page, downloads page, and a docs skeleton, with a small dark stylesheet. - A `nightly-builds` shortcode renders the build log; the downloads page shows the most recent nightly APKs, newest first, with per-build status. - Ships a custom-domain CNAME (taskwarrior.ccextractor.org). Build log - scripts/update_build_log.py prepends {ts, sha, msg, status, artifact, build} to website/data/nightly_builds.json (keeps the 90 most recent). The file ships empty ([]) and is populated by CI. CI - build-nightly.yml: daily at 02:00 UTC (+ manual) — builds the signed nightly APK, records the result via the collector, and commits the log to main. - deploy-website.yml: on pushes touching website/** — builds with Hugo and deploys to GitHub Pages via actions/deploy-pages. - nightlydepolyci.yml: ignore website/** and scripts/** so a build-log commit does not trigger a redundant APK rebuild. Verified locally: warning-free `hugo --minify` build (Hugo 0.164.0), and the collector + shortcode round-trip (entry renders on the downloads page; empty state renders when the log is []). Deviation from the proposal, documented in website/README.md: uses the modern GitHub Actions Pages deployment instead of renaming fdroid-repo -> public-site, so the site source stays in website/ on main with no dedicated deploy branch. Manual infra steps (enable Pages, DNS, branch protection) are listed there. --- .github/workflows/build-nightly.yml | 85 +++++++++++ .github/workflows/deploy-website.yml | 64 +++++++++ .github/workflows/nightlydepolyci.yml | 7 + scripts/update_build_log.py | 61 ++++++++ website/.gitignore | 4 + website/README.md | 74 ++++++++++ website/content/_index.md | 12 ++ website/content/docs/_index.md | 26 ++++ website/content/downloads.md | 20 +++ website/data/nightly_builds.json | 1 + website/hugo.toml | 30 ++++ website/layouts/_default/baseof.html | 29 ++++ website/layouts/_default/list.html | 13 ++ website/layouts/_default/single.html | 6 + website/layouts/index.html | 29 ++++ .../layouts/shortcodes/nightly-builds.html | 37 +++++ website/static/CNAME | 1 + website/static/css/style.css | 132 ++++++++++++++++++ 18 files changed, 631 insertions(+) create mode 100644 .github/workflows/build-nightly.yml create mode 100644 .github/workflows/deploy-website.yml create mode 100644 scripts/update_build_log.py create mode 100644 website/.gitignore create mode 100644 website/README.md create mode 100644 website/content/_index.md create mode 100644 website/content/docs/_index.md create mode 100644 website/content/downloads.md create mode 100644 website/data/nightly_builds.json create mode 100644 website/hugo.toml create mode 100644 website/layouts/_default/baseof.html create mode 100644 website/layouts/_default/list.html create mode 100644 website/layouts/_default/single.html create mode 100644 website/layouts/index.html create mode 100644 website/layouts/shortcodes/nightly-builds.html create mode 100644 website/static/CNAME create mode 100644 website/static/css/style.css diff --git a/.github/workflows/build-nightly.yml b/.github/workflows/build-nightly.yml new file mode 100644 index 00000000..3050b473 --- /dev/null +++ b/.github/workflows/build-nightly.yml @@ -0,0 +1,85 @@ +name: Nightly Build & Log + +# Builds the signed nightly APK on a schedule and records the result in the +# website build log (website/data/nightly_builds.json). Committing that file to +# main touches website/**, which triggers deploy-website.yml to refresh the +# published site. This complements the push-triggered F-Droid deploy +# (nightlydepolyci.yml); the two can be unified later. + +on: + schedule: + - cron: '0 2 * * *' # 02:00 UTC daily + workflow_dispatch: + +permissions: + contents: write # commit the updated build log back to main + +jobs: + nightly: + name: Build nightly APK and record build log + runs-on: ubuntu-latest + steps: + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@main + with: + tool-cache: false + android: false # keep Android SDKs for Flutter + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: true + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17.x' + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.29.2' + + - name: Get dependencies + run: flutter pub get + + - name: Decode signing secrets + env: + NIGHTLY_KEYSTORE_B64: ${{ secrets.NIGHTLY_KEYSTORE_B64 }} + NIGHTLY_PROPERTIES_B64: ${{ secrets.NIGHTLY_PROPERTIES_B64 }} + run: | + echo "$NIGHTLY_KEYSTORE_B64" | base64 --decode > android/nightly.jks + echo "$NIGHTLY_PROPERTIES_B64" | base64 --decode > android/key_nightly.properties + + - name: Build nightly APK + id: build + run: flutter build apk --flavor nightly --build-number=${{ github.run_number }} --release + + - name: Record successful build + if: success() + run: | + python3 scripts/update_build_log.py success \ + "https://github.com/${{ github.repository }}/raw/fdroid-repo/repo/nightly.${{ github.run_number }}.apk" \ + "${{ github.run_number }}" + + - name: Record failed build + if: failure() + run: python3 scripts/update_build_log.py failure "" "${{ github.run_number }}" + + - name: Commit build log + if: always() + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + if git diff --quiet -- website/data/nightly_builds.json; then + echo "No build-log changes to commit." + else + git add website/data/nightly_builds.json + git commit -m "chore(nightly): record build ${{ github.run_number }}" + git push + fi diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml new file mode 100644 index 00000000..b5fa4d79 --- /dev/null +++ b/.github/workflows/deploy-website.yml @@ -0,0 +1,64 @@ +name: Deploy Website + +# Builds the Hugo site in website/ and publishes it to GitHub Pages. +# Triggered when the site content or this workflow changes (the nightly +# build-log commit made by build-nightly.yml touches website/data/, so a new +# nightly automatically refreshes the published site). + +on: + push: + branches: [main] + paths: + - 'website/**' + - '.github/workflows/deploy-website.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +# Allow one concurrent deployment; don't cancel an in-progress production deploy. +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + env: + HUGO_VERSION: 0.164.0 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # hugo.toml enableGitInfo needs full history + + - name: Setup Hugo + uses: peaceiris/actions-hugo@v3 + with: + hugo-version: ${{ env.HUGO_VERSION }} + extended: true + + - name: Configure Pages + id: pages + uses: actions/configure-pages@v5 + + - name: Build site + run: hugo --source website --minify --baseURL "${{ steps.pages.outputs.base_url }}/" + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: website/public + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/nightlydepolyci.yml b/.github/workflows/nightlydepolyci.yml index e47359b8..c8e92ad4 100644 --- a/.github/workflows/nightlydepolyci.yml +++ b/.github/workflows/nightlydepolyci.yml @@ -4,6 +4,13 @@ on: push: branches: - main + # Website content and the nightly build-log commit must not trigger a full + # APK rebuild + F-Droid redeploy (see build-nightly.yml / deploy-website.yml). + paths-ignore: + - 'website/**' + - 'scripts/**' + - '.github/workflows/deploy-website.yml' + - '.github/workflows/build-nightly.yml' jobs: build-and-deploy: diff --git a/scripts/update_build_log.py b/scripts/update_build_log.py new file mode 100644 index 00000000..a2542037 --- /dev/null +++ b/scripts/update_build_log.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Record a nightly build in the website's build-log data file. + +Prepends an entry describing the current commit to +``website/data/nightly_builds.json`` (newest first) and keeps only the most +recent ``MAX_ENTRIES``. The Hugo ``nightly-builds`` shortcode renders this file. + +Run from the repository root (as the CI workflow does): + + python3 scripts/update_build_log.py + +All arguments are optional: + status "success" (default) or "failure" + artifact_url link to the built APK ("" if none) + build_number CI run number ("" if unknown) +""" + +import json +import subprocess +import sys +from datetime import datetime, timezone +from pathlib import Path + +LOG = Path("website/data/nightly_builds.json") +MAX_ENTRIES = 90 + + +def _git(*args: str) -> str: + return subprocess.check_output(["git", *args]).decode().strip() + + +def main(status: str = "success", artifact: str = "", build_number: str = "") -> None: + entry = { + "ts": datetime.now(timezone.utc).isoformat(timespec="seconds"), + "sha": _git("rev-parse", "HEAD")[:8], + "msg": _git("log", "-1", "--pretty=%s"), + "status": status, + "artifact": artifact, + "build": build_number, + } + + entries = [] + if LOG.exists(): + try: + entries = json.loads(LOG.read_text() or "[]") + except json.JSONDecodeError: + entries = [] + + entries.insert(0, entry) + entries = entries[:MAX_ENTRIES] + + LOG.parent.mkdir(parents=True, exist_ok=True) + LOG.write_text(json.dumps(entries, indent=2) + "\n") + print( + f"Recorded nightly build {entry['sha']} ({status}); " + f"{len(entries)} entr{'y' if len(entries) == 1 else 'ies'} in {LOG}." + ) + + +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 00000000..454de3e4 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,4 @@ +# Hugo build output and caches +/public/ +/resources/ +.hugo_build.lock diff --git a/website/README.md b/website/README.md new file mode 100644 index 00000000..0240d6ba --- /dev/null +++ b/website/README.md @@ -0,0 +1,74 @@ +# TaskWarrior Mobile — Project Website + +A self-contained [Hugo](https://gohugo.io) static site for the TaskWarrior Mobile +app: landing page, downloads (with an auto-updated **nightly build log**), and a +documentation section. It has **no external theme** — all layouts live in +`layouts/`, so it builds from the single Hugo binary with no submodules or network +fetches. + +## Local development + +Requires Hugo **extended** (pinned to the version in +[`.github/workflows/deploy-website.yml`](../.github/workflows/deploy-website.yml)): + +```bash +# from this directory +hugo server # live-reload dev server at http://localhost:1313 +hugo --minify # production build into ./public +``` + +## Layout + +``` +website/ +├── hugo.toml # site config (baseURL, menus, params) +├── content/ # Markdown pages (_index, downloads, docs) +├── layouts/ # self-contained templates (no theme) +│ ├── _default/ # baseof / single / list +│ ├── index.html # homepage +│ └── shortcodes/ +│ └── nightly-builds.html # renders data/nightly_builds.json +├── data/ +│ └── nightly_builds.json # build log (populated by CI; ships empty) +└── static/ + ├── css/style.css + └── CNAME # custom domain +``` + +## Nightly build log + +[`scripts/update_build_log.py`](../scripts/update_build_log.py) prepends an entry +`{ts, sha, msg, status, artifact, build}` to `data/nightly_builds.json` (keeping the +90 most recent). The `{{}}` shortcode on the downloads page +renders it. The file ships as `[]`; CI fills it in. + +## CI + +- **`build-nightly.yml`** — daily at 02:00 UTC (and on demand): builds the signed + nightly APK, records the result via `update_build_log.py`, and commits the updated + log to `main`. +- **`deploy-website.yml`** — on any push touching `website/**` (including the nightly + log commit): builds the site with Hugo and deploys it to GitHub Pages. +- `nightlydepolyci.yml` now ignores `website/**` and `scripts/**` so a build-log + commit does not trigger a redundant APK rebuild. + +## One-time infrastructure setup (needs repo-admin / DNS access) + +These cannot be done from code and must be configured by a maintainer: + +1. **Enable GitHub Pages** → repo *Settings → Pages → Build and deployment → + Source: **GitHub Actions***. +2. **Custom domain / DNS** — point `taskwarrior.ccextractor.org` at GitHub Pages + with a `CNAME` DNS record to `.github.io`. The site already ships a + `static/CNAME`, so Pages will pick up the domain on first deploy. +3. **Branch protection** — if `main` is protected, allow `github-actions[bot]` to + push the nightly build-log commit (or point that commit at an unprotected branch). +4. The nightly signing secrets (`NIGHTLY_KEYSTORE_B64`, `NIGHTLY_PROPERTIES_B64`) are + the same ones the existing F-Droid workflow already uses. + +> **Note on the deploy target.** The proposal described renaming the `fdroid-repo` +> branch to `public-site` and serving from a branch. This implementation instead uses +> the modern **GitHub Actions Pages deployment** (`actions/deploy-pages`), which keeps +> the site source in `website/` on `main` and needs no dedicated deploy branch — fewer +> moving parts and no history juggling. The F-Droid APK repo on `fdroid-repo` is +> untouched. diff --git a/website/content/_index.md b/website/content/_index.md new file mode 100644 index 00000000..dd48717b --- /dev/null +++ b/website/content/_index.md @@ -0,0 +1,12 @@ +--- +title: "TaskWarrior Mobile" +--- + +**TaskWarrior Mobile** is the cross-platform [Flutter](https://flutter.dev) client for +[TaskWarrior](https://taskwarrior.org), maintained by [CCExtractor](https://ccextractor.org). +It brings TaskWarrior's fast, unobtrusive task management to Android, iOS, Windows, +macOS, and Linux, backed by [TaskChampion](https://github.com/GothenburgBitFactory/taskchampion) +for native, offline-first sync. + +New here? Head to the [downloads page](downloads/) to install the latest build, or browse +the [nightly build history](downloads/#nightly-builds) to see what has changed recently. diff --git a/website/content/docs/_index.md b/website/content/docs/_index.md new file mode 100644 index 00000000..5d0cc310 --- /dev/null +++ b/website/content/docs/_index.md @@ -0,0 +1,26 @@ +--- +title: "Documentation" +description: "Developer and user documentation for TaskWarrior Mobile." +--- + +Documentation for TaskWarrior Mobile. This section is a living skeleton that grows +alongside the app. + +## Architecture at a glance + +- **UI** — Flutter (Dart) with GetX for state management and routing. +- **Core** — a Rust FFI bridge (`tc_helper`, via `flutter_rust_bridge`) that wraps + [TaskChampion](https://github.com/GothenburgBitFactory/taskchampion) for task storage. +- **Sync** — offline-first: changes hit a local SQLite/TaskChampion replica immediately + and synchronise with a TaskChampion sync server when connectivity is available. + +## Getting the app + +See the [downloads page](../downloads/) for stable releases and nightly builds. + +## Contributing + +Contributions are welcome. Start with the +[CONTRIBUTING guide](https://github.com/CCExtractor/taskwarrior-flutter/blob/main/CONTRIBUTING.md) +in the repository, and join the [CCExtractor community](https://ccextractor.org/) on Slack +or Zulip. diff --git a/website/content/downloads.md b/website/content/downloads.md new file mode 100644 index 00000000..5029cd30 --- /dev/null +++ b/website/content/downloads.md @@ -0,0 +1,20 @@ +--- +title: "Downloads" +description: "Install TaskWarrior Mobile — stable releases and automatically-built nightly APKs." +--- + +## Stable releases + +Tagged releases are published on GitHub. Download the latest signed APK from the +[releases page](https://github.com/CCExtractor/taskwarrior-flutter/releases). + +## Nightly builds {#nightly-builds} + +A signed nightly APK is built automatically from the `main` branch every night and +published to the project's F-Droid repository. Each entry below links to the APK produced +by that build, newest first. + +{{< nightly-builds limit="20" >}} + +_Nightly builds are unstable snapshots intended for testing. For everyday use, prefer a +stable release above._ diff --git a/website/data/nightly_builds.json b/website/data/nightly_builds.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/website/data/nightly_builds.json @@ -0,0 +1 @@ +[] diff --git a/website/hugo.toml b/website/hugo.toml new file mode 100644 index 00000000..c66d1dfd --- /dev/null +++ b/website/hugo.toml @@ -0,0 +1,30 @@ +baseURL = "https://taskwarrior.ccextractor.org/" +locale = "en-us" +title = "TaskWarrior Mobile" +enableGitInfo = true + +# Self-contained site: layouts live in ./layouts, so no external theme (and thus +# no submodule / network fetch) is required — Hugo builds it from its single +# binary alone. + +[params] + description = "The cross-platform TaskWarrior mobile app by CCExtractor — offline-first task management with native TaskChampion sync." + repo = "https://github.com/CCExtractor/taskwarrior-flutter" + org = "https://ccextractor.org/" + +[markup.goldmark.renderer] + unsafe = true + +[menu] + [[menu.main]] + name = "Home" + url = "/" + weight = 1 + [[menu.main]] + name = "Downloads" + url = "/downloads/" + weight = 2 + [[menu.main]] + name = "Docs" + url = "/docs/" + weight = 3 diff --git a/website/layouts/_default/baseof.html b/website/layouts/_default/baseof.html new file mode 100644 index 00000000..0f314d08 --- /dev/null +++ b/website/layouts/_default/baseof.html @@ -0,0 +1,29 @@ + + + + + + {{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} · {{ .Site.Title }}{{ end }} + + + + + + +
+ {{ block "main" . }}{{ end }} +
+ +
+

{{ .Site.Title }} · maintained by CCExtractor · built with Hugo

+
+ + diff --git a/website/layouts/_default/list.html b/website/layouts/_default/list.html new file mode 100644 index 00000000..8932309c --- /dev/null +++ b/website/layouts/_default/list.html @@ -0,0 +1,13 @@ +{{ define "main" }} +
+

{{ .Title }}

+ {{ .Content }} + {{ with .Pages }} + + {{ end }} +
+{{ end }} diff --git a/website/layouts/_default/single.html b/website/layouts/_default/single.html new file mode 100644 index 00000000..2eee85e2 --- /dev/null +++ b/website/layouts/_default/single.html @@ -0,0 +1,6 @@ +{{ define "main" }} +
+

{{ .Title }}

+ {{ .Content }} +
+{{ end }} diff --git a/website/layouts/index.html b/website/layouts/index.html new file mode 100644 index 00000000..3e9b59d5 --- /dev/null +++ b/website/layouts/index.html @@ -0,0 +1,29 @@ +{{ define "main" }} +
+

Your tasks, everywhere.

+

{{ .Site.Params.description }}

+

+ Get the app + View source +

+
+ +
+
+

Offline-first

+

Every create, edit, and complete is written to a local database instantly — no spinner, no network wait.

+
+
+

Native sync

+

Synchronise with a TaskChampion sync server through the native Rust bridge whenever you are online.

+
+
+

Cross-platform

+

One app across Android, iOS, Windows, macOS, and Linux, built with Flutter.

+
+
+ +
+ {{ .Content }} +
+{{ end }} diff --git a/website/layouts/shortcodes/nightly-builds.html b/website/layouts/shortcodes/nightly-builds.html new file mode 100644 index 00000000..3788f4ea --- /dev/null +++ b/website/layouts/shortcodes/nightly-builds.html @@ -0,0 +1,37 @@ +{{/* + nightly-builds shortcode — renders the build log collected by + scripts/update_build_log.py into website/data/nightly_builds.json. + Usage in content: {{< nightly-builds >}} (optionally {{< nightly-builds limit="15" >}}) +*/}} +{{ $builds := hugo.Data.nightly_builds }} +{{ $limit := int (.Get "limit" | default "20") }} +
+ {{ if $builds }} +
+ + + + + + + + + + + + {{ range first $limit $builds }} + + + + + + + + {{ end }} + +
Date (UTC)CommitMessageStatusAPK
{{ .ts }}{{ .sha }}{{ .msg }}{{ .status }}{{ with .artifact }}download{{ else }}—{{ end }}
+
+ {{ else }} +

No nightly builds have been recorded yet. Check back after the next scheduled build.

+ {{ end }} +
diff --git a/website/static/CNAME b/website/static/CNAME new file mode 100644 index 00000000..f71f76ef --- /dev/null +++ b/website/static/CNAME @@ -0,0 +1 @@ +taskwarrior.ccextractor.org diff --git a/website/static/css/style.css b/website/static/css/style.css new file mode 100644 index 00000000..4faed56e --- /dev/null +++ b/website/static/css/style.css @@ -0,0 +1,132 @@ +:root { + --bg: #0f1419; + --surface: #171d26; + --text: #e6e9ee; + --muted: #9aa4b2; + --accent: #7c5cff; + --accent-2: #4dd0e1; + --border: #263041; + --ok: #3fb950; + --fail: #f85149; + --radius: 12px; + --maxw: 900px; +} + +* { box-sizing: border-box; } + +html { -webkit-text-size-adjust: 100%; } + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + background: var(--bg); + color: var(--text); + line-height: 1.6; +} + +a { color: var(--accent-2); text-decoration: none; } +a:hover { text-decoration: underline; } + +code { + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 6px; + padding: 0.1em 0.4em; + font-size: 0.9em; +} + +/* Header */ +.site-header { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 1rem 1.5rem; + border-bottom: 1px solid var(--border); + background: var(--surface); +} +.brand { font-weight: 700; font-size: 1.15rem; color: var(--text); } +.brand span { color: var(--accent); } +.site-header nav { display: flex; flex-wrap: wrap; gap: 1.1rem; } +.site-header nav a { color: var(--muted); font-weight: 500; } +.site-header nav a:hover { color: var(--text); text-decoration: none; } + +/* Layout */ +.content { max-width: var(--maxw); margin: 0 auto; padding: 2.5rem 1.5rem 4rem; } + +/* Hero */ +.hero { text-align: center; padding: 2rem 0 1rem; } +.hero h1 { font-size: clamp(2rem, 6vw, 3rem); margin: 0 0 0.5rem; } +.tagline { color: var(--muted); font-size: 1.15rem; max-width: 640px; margin: 0 auto 1.5rem; } +.cta { display: flex; gap: 0.75rem; justify-content: center; flex-wrap: wrap; } + +.button { + display: inline-block; + background: var(--accent); + color: #fff; + padding: 0.7rem 1.4rem; + border-radius: var(--radius); + font-weight: 600; +} +.button:hover { filter: brightness(1.1); text-decoration: none; } +.button-ghost { background: transparent; color: var(--text); border: 1px solid var(--border); } + +/* Features */ +.features { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 1rem; + margin: 2.5rem 0; +} +.feature { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 1.25rem; +} +.feature h3 { margin: 0 0 0.4rem; color: var(--accent-2); } +.feature p { margin: 0; color: var(--muted); } + +/* Pages */ +.page h1 { border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; } +.page-list { padding-left: 1.2rem; } + +/* Nightly build table */ +.table-scroll { overflow-x: auto; } +.nightly-builds table { + width: 100%; + border-collapse: collapse; + margin: 1rem 0; + font-size: 0.92rem; +} +.nightly-builds th, .nightly-builds td { + text-align: left; + padding: 0.55rem 0.7rem; + border-bottom: 1px solid var(--border); + vertical-align: top; +} +.nightly-builds th { color: var(--muted); font-weight: 600; } +.nowrap { white-space: nowrap; } +.empty { color: var(--muted); font-style: italic; } + +.status { + display: inline-block; + padding: 0.1em 0.6em; + border-radius: 999px; + font-size: 0.8em; + font-weight: 600; + text-transform: capitalize; +} +.status-success { background: rgba(63,185,80,0.15); color: var(--ok); } +.status-failure { background: rgba(248,81,73,0.15); color: var(--fail); } + +/* Footer */ +.site-footer { + border-top: 1px solid var(--border); + padding: 1.5rem; + text-align: center; + color: var(--muted); + font-size: 0.9rem; +} From a81dc6d8181a26c6735ba09382a483efa445557b Mon Sep 17 00:00:00 2001 From: Chinmay Chaudhari Date: Wed, 15 Jul 2026 22:37:56 +0530 Subject: [PATCH 2/2] docs(website): add Features page, Docs guides, and content styling Expand the site beyond the initial skeleton: - New Features page (wired into the nav) covering the full task model, offline-first behaviour, native TaskChampion sync, and cross-platform reach. - New Docs guides that auto-list on /docs/: getting-started, sync-setup, architecture (with a data-flow diagram), and an FAQ. - Home page: three more feature cards + an "Explore all features" link. - CSS: style markdown tables, code blocks, and blockquotes on content pages. --- website/content/docs/_index.md | 20 ++++------- website/content/docs/architecture.md | 42 +++++++++++++++++++++++ website/content/docs/faq.md | 39 ++++++++++++++++++++++ website/content/docs/getting-started.md | 31 +++++++++++++++++ website/content/docs/sync-setup.md | 40 ++++++++++++++++++++++ website/content/features.md | 44 +++++++++++++++++++++++++ website/hugo.toml | 8 +++-- website/layouts/index.html | 16 +++++++++ website/static/css/style.css | 33 +++++++++++++++++++ 9 files changed, 257 insertions(+), 16 deletions(-) create mode 100644 website/content/docs/architecture.md create mode 100644 website/content/docs/faq.md create mode 100644 website/content/docs/getting-started.md create mode 100644 website/content/docs/sync-setup.md create mode 100644 website/content/features.md diff --git a/website/content/docs/_index.md b/website/content/docs/_index.md index 5d0cc310..d6268b0f 100644 --- a/website/content/docs/_index.md +++ b/website/content/docs/_index.md @@ -3,20 +3,10 @@ title: "Documentation" description: "Developer and user documentation for TaskWarrior Mobile." --- -Documentation for TaskWarrior Mobile. This section is a living skeleton that grows -alongside the app. - -## Architecture at a glance - -- **UI** — Flutter (Dart) with GetX for state management and routing. -- **Core** — a Rust FFI bridge (`tc_helper`, via `flutter_rust_bridge`) that wraps - [TaskChampion](https://github.com/GothenburgBitFactory/taskchampion) for task storage. -- **Sync** — offline-first: changes hit a local SQLite/TaskChampion replica immediately - and synchronise with a TaskChampion sync server when connectivity is available. - -## Getting the app - -See the [downloads page](../downloads/) for stable releases and nightly builds. +Documentation for TaskWarrior Mobile — a living set of guides that grows alongside the +app. New here? Start with [Getting started](getting-started/), then +[set up sync](sync-setup/) when you are ready to connect a server. Curious how it all +fits together? See the [architecture overview](architecture/). ## Contributing @@ -24,3 +14,5 @@ Contributions are welcome. Start with the [CONTRIBUTING guide](https://github.com/CCExtractor/taskwarrior-flutter/blob/main/CONTRIBUTING.md) in the repository, and join the [CCExtractor community](https://ccextractor.org/) on Slack or Zulip. + +## Guides diff --git a/website/content/docs/architecture.md b/website/content/docs/architecture.md new file mode 100644 index 00000000..394dc050 --- /dev/null +++ b/website/content/docs/architecture.md @@ -0,0 +1,42 @@ +--- +title: "Architecture" +description: "How TaskWarrior Mobile is built — Flutter UI over a native Rust core." +weight: 3 +--- + +TaskWarrior Mobile is a Flutter app sitting on top of a native Rust core, so the same +task engine that powers TaskWarrior on the desktop runs unchanged on your phone. + +## Layers + +- **UI — Flutter (Dart).** Screens, navigation, and state management use + [GetX](https://pub.dev/packages/get). This layer never talks to the network for task + data; it reads and writes through the core. +- **Core — Rust (`tc_helper`).** A thin wrapper around + [TaskChampion](https://github.com/GothenburgBitFactory/taskchampion) exposed to Dart + through [`flutter_rust_bridge`](https://github.com/fzyzcjy/flutter_rust_bridge). It + owns task storage, mutations, and sync. +- **Storage & sync — TaskChampion.** Tasks live in a local replica on the device. + Syncing reconciles that replica with a TaskChampion sync server; there is no + bespoke HTTP API in between. + +## Offline-first data flow + +``` +tap "complete" ─▶ Flutter ─▶ Rust FFI ─▶ local replica (instant, no network) + │ + (later, when online) + ▼ + TaskChampion sync server +``` + +Every create, edit, and complete is committed to the local replica synchronously, so the +UI never waits on the network. Synchronisation is a separate, best-effort step that runs +when connectivity is available. + +## Why a Rust core? + +Reusing TaskChampion means the mobile app shares TaskWarrior's battle-tested task model — +recurrence, dependencies, annotations, tags, and the sync protocol — instead of +reimplementing them. The Rust bridge surfaces those capabilities to the Flutter UI with a +small, typed FFI surface. diff --git a/website/content/docs/faq.md b/website/content/docs/faq.md new file mode 100644 index 00000000..c308c347 --- /dev/null +++ b/website/content/docs/faq.md @@ -0,0 +1,39 @@ +--- +title: "FAQ" +description: "Frequently asked questions about TaskWarrior Mobile." +weight: 4 +--- + +## Do I need a sync server to use the app? + +No. TaskWarrior Mobile is fully usable offline — tasks are stored in a local replica on +your device. A sync server is only needed if you want your tasks mirrored to another +device or backed up remotely. + +## Is my data encrypted? + +Yes. When you sync, your tasks are encrypted locally with your encryption secret before +being uploaded. The server stores only ciphertext; only clients that hold the secret can +read your tasks. + +## Which platforms are supported? + +The app targets Android, iOS, Windows, macOS, and Linux from a single Flutter codebase. +Android builds are published as APKs on the [downloads page](../../downloads/). + +## Does it work with my existing TaskWarrior setup? + +If your TaskWarrior 3.x desktop syncs through a TaskChampion sync server, point the app at +the same server, client ID, and encryption secret and your tasks will sync between them. +See [setting up sync](../sync-setup/). + +## What's the difference between stable and nightly builds? + +Stable builds are tagged releases meant for everyday use. Nightly builds are automatic +snapshots of the `main` branch — useful for trying the latest changes, but less tested. + +## Where do I report a bug or request a feature? + +Open an issue on the +[GitHub repository](https://github.com/CCExtractor/taskwarrior-flutter/issues). Pull +requests are welcome too — see the [contributing notes](../../docs/#contributing). diff --git a/website/content/docs/getting-started.md b/website/content/docs/getting-started.md new file mode 100644 index 00000000..cc9b29b9 --- /dev/null +++ b/website/content/docs/getting-started.md @@ -0,0 +1,31 @@ +--- +title: "Getting started" +description: "Install TaskWarrior Mobile and create your first task." +weight: 1 +--- + +## Install the app + +Grab a build from the [downloads page](../../downloads/): + +- **Stable** — the latest signed APK from the GitHub [releases page](https://github.com/CCExtractor/taskwarrior-flutter/releases). +- **Nightly** — an automatically-built snapshot from the `main` branch, for testing the newest changes. + +On Android you may need to allow installation from your browser or file manager the +first time you sideload an APK. + +## Create your first task + +1. Open the app — you land on the task list. +2. Tap the **+** button. +3. Give the task a description, and optionally a project, tags, priority, and due date. +4. Save. The task is written to the local database immediately — no network required. + +Everything you do works fully offline. Sync is optional and layered on top; set it up +whenever you want your tasks mirrored to a server or another device. + +## Next steps + +- [Set up sync](../sync-setup/) with a TaskChampion server. +- Learn how the app is [put together](../architecture/). +- Browse the [FAQ](../faq/) for common questions. diff --git a/website/content/docs/sync-setup.md b/website/content/docs/sync-setup.md new file mode 100644 index 00000000..535328b6 --- /dev/null +++ b/website/content/docs/sync-setup.md @@ -0,0 +1,40 @@ +--- +title: "Setting up sync" +description: "Connect TaskWarrior Mobile to a TaskChampion sync server." +weight: 2 +--- + +TaskWarrior Mobile syncs through a [TaskChampion](https://github.com/GothenburgBitFactory/taskchampion) +sync server — the same protocol used by TaskWarrior 3.x on the desktop. Sync is +**optional and offline-first**: your tasks always live in a local replica, and syncing +simply reconciles that replica with the server when you are online. + +## What you need + +Three values from your TaskChampion sync server: + +| Field | Description | +|---|---| +| **Sync server URL** | The base URL of the TaskChampion server, e.g. `https://example.org/taskchampion/`. | +| **Client ID** | A UUID identifying your replica on the server. | +| **Encryption secret** | The secret used to encrypt your data end-to-end. Only clients that share it can read your tasks. | + +The server never sees your task data in the clear — everything is encrypted locally with +your encryption secret before it is uploaded. + +## Connecting + +1. Open **Profile** → change the sync server → choose **Taskchampion**. +2. Tap **Configure** and paste the three values above. +3. Tap **Save**. The app performs a live sync against the server to confirm the + credentials work *before* it stores them — if anything is wrong, you find out + immediately instead of silently failing later. + +Once connected, changes flow both ways whenever you have connectivity. You can keep +working offline at any time; the next sync catches the server up. + +## Self-hosting a server + +Any TaskChampion-compatible sync server works. See the upstream +[TaskChampion sync-server](https://github.com/GothenburgBitFactory/taskchampion-sync-server) +project to run your own. diff --git a/website/content/features.md b/website/content/features.md new file mode 100644 index 00000000..6a0daf04 --- /dev/null +++ b/website/content/features.md @@ -0,0 +1,44 @@ +--- +title: "Features" +description: "What TaskWarrior Mobile can do — offline-first task management with native TaskChampion sync." +--- + +TaskWarrior Mobile brings the full TaskWarrior task model to your phone and desktop, +without giving up speed or working offline. + +## Task management + +- **Rich tasks** — descriptions, projects, tags, priorities, and due dates. +- **Recurring tasks** — repeat tasks on a schedule. +- **Dependencies** — mark tasks as blocking or blocked by others so you always know + what is actionable. +- **Annotations** — attach timestamped notes to any task. +- **Fast capture and edit** — add or change a task in a couple of taps. + +## Offline-first + +- Every create, edit, and complete is written to a **local replica instantly** — no + spinner, no waiting on the network. +- The app is fully usable with no connectivity at all; sync is optional. + +## Native TaskChampion sync + +- Syncs through a [TaskChampion](https://github.com/GothenburgBitFactory/taskchampion) + sync server — the same protocol used by TaskWarrior 3.x on the desktop. +- **End-to-end encrypted**: your tasks are encrypted locally before upload, so the server + only ever stores ciphertext. +- Credentials are **validated against the server before they are saved**, so a bad URL or + secret is caught immediately. + +## Cross-platform + +- One Flutter codebase targeting **Android, iOS, Windows, macOS, and Linux**. +- A shared native Rust core keeps behaviour consistent everywhere. + +## Open source + +- Developed in the open on [GitHub](https://github.com/CCExtractor/taskwarrior-flutter) + and maintained by [CCExtractor](https://ccextractor.org/). +- Automated [nightly builds](../downloads/#nightly-builds) let you try the latest changes. + +Ready to try it? Head to the [downloads page](../downloads/). diff --git a/website/hugo.toml b/website/hugo.toml index c66d1dfd..ca8ebeb3 100644 --- a/website/hugo.toml +++ b/website/hugo.toml @@ -20,11 +20,15 @@ enableGitInfo = true name = "Home" url = "/" weight = 1 + [[menu.main]] + name = "Features" + url = "/features/" + weight = 2 [[menu.main]] name = "Downloads" url = "/downloads/" - weight = 2 + weight = 3 [[menu.main]] name = "Docs" url = "/docs/" - weight = 3 + weight = 4 diff --git a/website/layouts/index.html b/website/layouts/index.html index 3e9b59d5..92dd3063 100644 --- a/website/layouts/index.html +++ b/website/layouts/index.html @@ -21,6 +21,22 @@

Native sync

Cross-platform

One app across Android, iOS, Windows, macOS, and Linux, built with Flutter.

+
+

End-to-end encrypted

+

Your tasks are encrypted on-device before they sync — the server only ever stores ciphertext it cannot read.

+
+
+

Full task model

+

Projects, tags, priorities, due dates, recurrence, dependencies, and annotations — the complete TaskWarrior model.

+
+
+

Open source

+

Developed in the open on GitHub and maintained by CCExtractor, with automated nightly builds.

+
+ + +
+ Explore all features →
diff --git a/website/static/css/style.css b/website/static/css/style.css index 4faed56e..37f1d885 100644 --- a/website/static/css/style.css +++ b/website/static/css/style.css @@ -91,8 +91,41 @@ code { /* Pages */ .page h1 { border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; } +.page h2 { margin-top: 2rem; } .page-list { padding-left: 1.2rem; } +/* Content tables (markdown) */ +.page table { + width: 100%; + border-collapse: collapse; + margin: 1.25rem 0; + font-size: 0.95rem; +} +.page th, .page td { + text-align: left; + padding: 0.55rem 0.7rem; + border-bottom: 1px solid var(--border); + vertical-align: top; +} +.page th { color: var(--muted); font-weight: 600; } +.page thead tr { border-bottom: 2px solid var(--border); } + +/* Code blocks */ +.page pre { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 1rem; + overflow-x: auto; +} +.page pre code { background: none; border: none; padding: 0; } +.page blockquote { + margin: 1.25rem 0; + padding: 0.25rem 1rem; + border-left: 3px solid var(--accent); + color: var(--muted); +} + /* Nightly build table */ .table-scroll { overflow-x: auto; } .nightly-builds table {