From 4b6fca39a8069e7ff6bc136b4dd0b706cbcc22df Mon Sep 17 00:00:00 2001 From: bvandeusen Date: Wed, 11 Mar 2026 23:24:48 -0400 Subject: [PATCH] ci: merge build into ci workflow with needs gate; update README --- .forgejo/workflows/build.yml | 85 ----------------------------------- .forgejo/workflows/ci.yml | 87 +++++++++++++++++++++++++++++++++++- README.md | 75 ++++++++++++++++++++++--------- 3 files changed, 141 insertions(+), 106 deletions(-) delete mode 100644 .forgejo/workflows/build.yml diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml deleted file mode 100644 index 628c80d..0000000 --- a/.forgejo/workflows/build.yml +++ /dev/null @@ -1,85 +0,0 @@ -# Branch push (dev): builds APK, uploads as artifact named fabledapp-dev- -# Branch push (main): builds APK, uploads as artifact named fabledapp- -# Tag push (v26.03.09): builds APK, uploads artifact + creates Forgejo Release -# -# To cut a release: -# git tag v26.03.09 && git push origin v26.03.09 -# -# Required secrets (repo → Settings → Secrets → Actions): -# RELEASE_TOKEN — Forgejo PAT with write:repository scope -name: Build APK - -on: - push: - branches: [main, dev] - tags: ["v*"] - paths: - - "lib/**" - - "pubspec.yaml" - - "pubspec.lock" - - "android/**" - - "assets/**" - - ".forgejo/workflows/build.yml" - -jobs: - build: - name: Build release APK - runs-on: py3.12-node22 - container: - image: ghcr.io/cirruslabs/flutter:stable - steps: - - uses: actions/checkout@v6 - - - name: Install dependencies - run: flutter pub get - - - name: Build release APK - run: flutter build apk --release - - - name: Set artifact name - id: artifact - run: | - if [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then - echo "name=fabledapp-dev-${{ github.sha }}" >> $GITHUB_OUTPUT - else - echo "name=fabledapp-${{ github.sha }}" >> $GITHUB_OUTPUT - fi - - - name: Upload artifact - uses: actions/upload-artifact@v6 - with: - name: ${{ steps.artifact.outputs.name }} - path: build/app/outputs/flutter-apk/app-release.apk - retention-days: 30 - - - name: Create Forgejo release - if: startsWith(github.ref, 'refs/tags/') - env: - RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} - TAG: ${{ github.ref_name }} - run: | - echo "Creating release $TAG..." - - RESPONSE=$(curl -s -X POST \ - "https://git.fabledsword.com/api/v1/repos/bvandeusen/FabledApp/releases" \ - -H "Authorization: token $RELEASE_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"tag_name\": \"$TAG\", \"name\": \"$TAG\", \"body\": \"\"}") - - RELEASE_ID=$(echo "$RESPONSE" | grep -oE '"id":[[:space:]]*[0-9]+' | head -1 | grep -oE '[0-9]+') - - if [ -z "$RELEASE_ID" ]; then - echo "Failed to create release. API response:" - echo "$RESPONSE" - exit 1 - fi - - echo "Release created with ID $RELEASE_ID, uploading APK..." - - curl -s -X POST \ - "https://git.fabledsword.com/api/v1/repos/bvandeusen/FabledApp/releases/$RELEASE_ID/assets" \ - -H "Authorization: token $RELEASE_TOKEN" \ - -F "attachment=@build/app/outputs/flutter-apk/app-release.apk" - - echo "Done — release $TAG is live at:" - echo "https://git.fabledsword.com/bvandeusen/FabledApp/releases/tag/$TAG" diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index be2189c..0679020 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -1,13 +1,29 @@ -name: CI +# CI runs first; build only proceeds if analyze and test pass. +# +# Push to dev: analyze + test → build APK → upload artifact (fabledapp-dev-) +# Push to main: analyze + test only (no build — wait for release tag) +# Tag v26.03.11: analyze + test → build APK → upload artifact + create Forgejo Release +# Pull request: analyze + test only +# +# To cut a release: +# git tag v26.03.11 && git push origin v26.03.11 +# +# Required secrets (repo → Settings → Secrets → Actions): +# RELEASE_TOKEN — Forgejo PAT with write:repository scope +name: CI & Build on: push: + branches: [main, dev] + tags: ["v*"] paths: - "lib/**" - "test/**" - "pubspec.yaml" - "pubspec.lock" - "analysis_options.yaml" + - "android/**" + - "assets/**" - ".forgejo/workflows/ci.yml" pull_request: paths: @@ -35,3 +51,72 @@ jobs: - name: Test run: flutter test + + build: + name: Build release APK + needs: [analyze] + # Build on dev branch pushes and version tag pushes only. + # main branch pushes run CI for safety but do not build — + # the release tag (v*) is the sole trigger for a production APK. + if: | + github.event_name == 'push' && + (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) + runs-on: py3.12-node22 + container: + image: ghcr.io/cirruslabs/flutter:stable + steps: + - uses: actions/checkout@v6 + + - name: Install dependencies + run: flutter pub get + + - name: Build release APK + run: flutter build apk --release + + - name: Set artifact name + id: artifact + run: | + if [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then + echo "name=fabledapp-dev-${{ github.sha }}" >> $GITHUB_OUTPUT + else + echo "name=fabledapp-${{ github.sha }}" >> $GITHUB_OUTPUT + fi + + - name: Upload artifact + uses: actions/upload-artifact@v6 + with: + name: ${{ steps.artifact.outputs.name }} + path: build/app/outputs/flutter-apk/app-release.apk + retention-days: 30 + + - name: Create Forgejo release + if: startsWith(github.ref, 'refs/tags/') + env: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + TAG: ${{ github.ref_name }} + run: | + echo "Creating release $TAG..." + + RESPONSE=$(curl -s -X POST \ + "https://git.fabledsword.com/api/v1/repos/bvandeusen/FabledApp/releases" \ + -H "Authorization: token $RELEASE_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"tag_name\": \"$TAG\", \"name\": \"$TAG\", \"body\": \"\"}") + + RELEASE_ID=$(echo "$RESPONSE" | grep -oE '"id":[[:space:]]*[0-9]+' | head -1 | grep -oE '[0-9]+') + + if [ -z "$RELEASE_ID" ]; then + echo "Failed to create release. API response:" + echo "$RESPONSE" + exit 1 + fi + + echo "Release created with ID $RELEASE_ID, uploading APK..." + + curl -s -X POST \ + "https://git.fabledsword.com/api/v1/repos/bvandeusen/FabledApp/releases/$RELEASE_ID/assets" \ + -H "Authorization: token $RELEASE_TOKEN" \ + -F "attachment=@build/app/outputs/flutter-apk/app-release.apk" + + echo "Done — release $TAG is live at:" + echo "https://git.fabledsword.com/bvandeusen/FabledApp/releases/tag/$TAG" diff --git a/README.md b/README.md index 1d7d0d9..ba254cf 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,21 @@ # Fabled — Android App -Native Android client for [FabledAssistant](https://github.com/yourusername/fabledassistant), a self-hosted AI productivity assistant. +Native Android client for FabledAssistant, a self-hosted AI second-brain and productivity assistant. ## Features -- **Notes** — create, edit, and browse markdown notes -- **Tasks** — manage tasks with status (To Do / In Progress / Done) and priority -- **Chat** — streaming AI conversations with real-time SSE response display -- **Quick Capture** — FAB shortcut to create a note or task from anywhere -- **OAuth / SSO** — authenticates via your server's configured OIDC provider; local username/password login also supported if enabled on the server -- **Session persistence** — stays logged in across app restarts via a persistent cookie jar -- **Home screen widget** — tap to open the chat screen directly from the Android launcher +- **Daily Briefing** — the primary screen; opens on launch. Shows your AI-compiled morning digest (tasks, calendar, weather, RSS) with a full conversation you can reply to inline. Refresh manually or browse past briefings from the overflow menu. +- **Quick Capture** — always-visible input bar above all tabs. Type a note or task and submit; multiple captures queue sequentially so the input is never blocked. Falls back to offline persistence when the server is unreachable. +- **Library** — unified browsable list of notes, tasks, and projects with filter pills (All · Notes · Tasks · Projects). Tasks have a secondary status sub-filter and a tappable status icon to cycle todo → in progress → done without opening the editor. Inline search filters results live. +- **Chat** — streaming AI conversations with real-time SSE display. Tap + to start a new conversation or open an existing one. +- **Note & task editing** — full Markdown editor for notes, task editor with due date, priority, project, and milestone assignment. +- **OAuth / SSO** — authenticates via your server's OIDC provider; local username/password login also supported if enabled server-side. +- **Session persistence** — stays logged in across restarts via a persistent cookie jar. +- **Auto-update** — checks your Forgejo releases on launch and prompts to download and install new APKs in-app. ## Requirements -- A running [FabledAssistant](https://github.com/yourusername/fabledassistant) server (self-hosted) +- A running FabledAssistant server (self-hosted) - Android 5.0+ (API 21) ## Getting Started @@ -38,20 +39,33 @@ On first launch, enter your FabledAssistant server URL (e.g. `https://fabled.exa ``` lib/ -├── main.dart # Entry point; resolves async deps before runApp -├── app.dart # GoRouter + auth redirect guards + shell nav +├── main.dart # Entry point; resolves async deps before runApp +├── app.dart # GoRouter + auth redirect guards + 3-tab shell ├── core/ -│ ├── constants.dart # Route name constants -│ └── exceptions.dart # AppException hierarchy +│ ├── constants.dart # Route name constants +│ ├── exceptions.dart # AppException hierarchy +│ └── theme.dart # Custom slate-indigo ColorScheme + Fraunces typography ├── data/ -│ ├── api/ # Dio HTTP layer (one class per resource) -│ ├── models/ # Plain Dart models with fromJson/toJson -│ └── repositories/ # Thin wrappers over API classes -└── providers/ # Riverpod providers (state + dependency wiring) - screens/ # Flutter UI screens +│ ├── api/ # Dio HTTP layer (one class per resource) +│ ├── models/ # Plain Dart models with fromJson/toJson +│ └── repositories/ # Thin wrappers over API classes +├── providers/ # Riverpod providers (state + dependency wiring) +│ ├── briefing_provider.dart # Today's briefing — optimistic UI, SSE, polling +│ └── capture_work_queue_provider.dart # Sequential in-memory capture queue +├── screens/ +│ ├── briefing/ # BriefingScreen + BriefingHistoryScreen +│ ├── library/ # LibraryScreen (unified notes/tasks/projects) +│ ├── chat/ # ConversationsTabScreen + ChatScreen +│ ├── notes/ # NoteDetailScreen + NoteEditScreen +│ ├── tasks/ # TaskEditScreen +│ └── settings/ auth/ setup/ splash/ +└── widgets/ + ├── chat_message_bubble.dart # Shared bubble (used by Chat + Briefing) + ├── briefing_digest_card.dart # Expandable first-message card + └── library_item_card.dart # Note / task / project row widgets ``` -**Key packages:** `flutter_riverpod`, `go_router`, `dio` + `cookie_jar`, `flutter_inappwebview`, `flutter_markdown` +**Key packages:** `flutter_riverpod`, `go_router`, `dio` + `cookie_jar`, `google_fonts`, `flutter_markdown_plus` ## Building a Release APK @@ -59,4 +73,25 @@ lib/ flutter build apk --release ``` -The signed APK will be at `build/app/outputs/flutter-apk/app-release.apk`. +The APK will be at `build/app/outputs/flutter-apk/app-release.apk`. + +## CI / CD + +CI and build are defined in a single workflow (`.forgejo/workflows/ci.yml`) running on the shared `py3.12-node22` act runner with the `ghcr.io/cirruslabs/flutter:stable` container. + +| Trigger | Analyze + Test | Build APK | Release | +|---------|---------------|-----------|---------| +| PR | ✓ | — | — | +| Push `dev` | ✓ | ✓ (artifact `fabledapp-dev-`) | — | +| Push `main` | ✓ | — | — | +| Tag `v*` | ✓ | ✓ (artifact `fabledapp-`) | ✓ Forgejo Release + APK attached | + +The build job has `needs: [analyze]` — a failed analyze or test blocks the APK build. + +To cut a release: + +```bash +git tag v26.03.11 && git push origin v26.03.11 +``` + +Requires a `RELEASE_TOKEN` secret (Forgejo PAT with `write:repository` scope) set in repo Settings → Secrets → Actions.