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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ app.*.map.json
/android/app/release

.snap-local/

# Local Snap packages contain compiled build-time configuration.
/*.snap
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ The local Snap helper accepts the same value with
`--dart-define BUSYSTACK_FEEDBACK_ENDPOINT=http://127.0.0.1:8090/api/feedback`.
No API, CAPTCHA, or other private server credential is used by the desktop application.

## Beta snap
## Build and publish the Snap

See [Beta Snap Release](docs/beta_snap_release.md) for local beta build,
install, and validation notes.
See [Snap Build and Beta Release](docs/beta_snap_release.md) for OAuth build
configuration, canonical Snapcraft packaging, local installation, artifact
verification, Store review, and beta release instructions.
218 changes: 159 additions & 59 deletions docs/beta_snap_release.md
Original file line number Diff line number Diff line change
@@ -1,80 +1,180 @@
# BusyMax Beta Snap Release
# BusyMax Snap Build and Beta Release

BusyMax `0.1.1` is a beta release target for public/listed visibility in
Ubuntu App Center and the Snap Store.
`snap/snapcraft.yaml` packages an existing Flutter Linux bundle. It does not
run `flutter build` or read `.snap-local/busymax-dart-defines.json`, so the
OAuth-enabled Flutter build must run first.

## Build
## Prepare

Required: Linux amd64, the Flutter Linux toolchain, snapd, Snapcraft with LXD,
`unsquashfs` from `squashfs-tools`, and BusyMax Store access for publishing.

Check that the package versions match:

```bash
grep -nE '^version:|<release version=' \
pubspec.yaml snap/snapcraft.yaml linux/io.busystack.busymax.metainfo.xml
```

`pubspec.yaml`, `snap/snapcraft.yaml`, and the newest metainfo release must
match. Older metainfo entries are history. Change these source files instead
of overriding the helper version, which would not change Flutter's embedded
version.

The snap build requires OAuth configuration at build time. Do not commit real
values.
Create the ignored OAuth file:

```bash
mkdir -p .snap-local
$EDITOR .snap-local/busymax-dart-defines.json
chmod 600 .snap-local/busymax-dart-defines.json
```

```json
{
"GOOGLE_OAUTH_CLIENT_ID": "your-google-client-id",
"GOOGLE_OAUTH_CLIENT_SECRET": "your-google-client-secret",
"MICROSOFT_OAUTH_CLIENT_ID": "your-microsoft-client-id"
}
```

See [Google Setup](google_setup.md) and
[Microsoft Setup](microsoft_setup.md). These values are embedded in the Snap
and can be extracted, so use only native Desktop/public-client credentials.
Never use server credentials or commit the JSON or generated `.snap` files.

## Build

From the repository root:

```bash
flutter pub get
flutter analyze
flutter test
flutter build linux --release \
--dart-define-from-file=.snap-local/busymax-dart-defines.json
snapcraft pack --use-lxd
```

The ignored `.snap-local/busymax-dart-defines.json` file must contain the
required Dart defines. The build fails if that local file is missing.
Snapcraft writes `busymax_<version>_amd64.snap`. Use the exact path it prints.
If the app says **This provider is not configured**, the bundle was built
without valid defines; reconnecting cannot fix it, so rebuild the package.

For a local scaffold smoke build instead, first quit every running BusyMax
instance, including its tray process and any development build:

```bash
./tools/build_install_snap_local.sh \
--dart-define-from-file .snap-local/busymax-dart-defines.json
```

The helper requires `/snap/busymax/current` or `--scaffold DIR`, repacks and
installs the local payload, and is not the canonical Store build above. Leave
`--root` at its safe default. It does not remove or purge app data. Its
`Defines: 1` output only confirms that one file argument was passed, not that
the required values are present. Use `--skip-tests` only for a repeat build of
the same commit after its tests passed; `--no-run` still installs the package.

## Install The Beta
## Verify Locally

For local validation:
Set the exact artifact path:

```bash
sudo snap install --dangerous busymax_0.1.1_amd64.snap
SNAP_FILE=./busymax_RELEASE_VERSION_amd64.snap
```

For store users after the revision is uploaded and released to beta:
Check its metadata and save its checksum:

```bash
unsquashfs -cat "$SNAP_FILE" meta/snap.yaml |
sed -n '/^name:/p;/^version:/p;/^grade:/p;/^confinement:/p'
sha256sum "$SNAP_FILE"
```

Check the top-level launchers:

```bash
unsquashfs -ll "$SNAP_FILE" |
sed -nE 's#^.*squashfs-root/meta/gui/([^/]+\.desktop)$#\1#p'
```

The output must contain exactly `busymax.desktop`.
`share/applications/io.busystack.busymax.desktop` is an expected internal file,
not a second top-level launcher.

Close BusyMax and its tray process, then install and launch the local package:

```bash
sudo snap install --dangerous "$SNAP_FILE"
snap connections busymax
snap run busymax
```

`--dangerous` bypasses Store signature checks, not strict confinement.

Before upload, verify:

- Desktop search shows one BusyMax launcher; both main and Agenda windows open.
- Google and Microsoft sign-in complete successfully.
- Tasks and events can be created, edited, completed, and deleted; a task
created in Agenda appears immediately without manual refresh.
- Accounts, settings, and data survive restart.
- Notifications and tray actions, including Agenda and Quit, work.

## Upload To Beta

Authenticate if needed, then upload once with the beta release target:

```bash
snapcraft login
snapcraft whoami
snapcraft upload --release=beta "$SNAP_FILE"
snapcraft revisions busymax --arch amd64
snapcraft status busymax --arch amd64
```

Save the numeric Store revision printed for the verified checksum. It is an
immutable upload identifier, separate from the app version. Do not re-upload
the artifact because review or release is pending.

The `busymax-dbus` session D-Bus slot may trigger manual review. If an older
revision blocks the new one, reject it only when it is obsolete; otherwise
wait or contact the
[Store reviewers](https://forum.snapcraft.io/c/store-requests/19). A
`resource-not-ready` or inconsistent-state error means nothing was released.
Check the [publisher dashboard](https://dashboard.snapcraft.io/) and retry only
after review clears.

If manual review completes but the revision was not automatically released,
release the exact reviewed revision:

```bash
snapcraft release busymax STORE_REVISION beta
snapcraft status busymax --arch amd64
```

The recipe currently has `grade: devel`, so only `beta` and `edge` are allowed.
Candidate or stable requires `grade: stable`, a rebuild, a new upload, and the
same verification.

## Verify The Store Revision

Prefer a separate test machine. For a fresh install:

```bash
sudo snap install busymax --beta
snap info busymax
snap run busymax
```

For an existing Store-tracking install:

```bash
sudo snap refresh busymax --channel=beta
snap info busymax
```

## Scope

- Snap confinement is strict.
- The tray/status-notifier feature and background-on-close behavior are enabled
by default for the beta. Users can disable them in settings.
- Tray support currently uses a vendored patched `xdg_status_notifier_item`
StatusNotifierItem/DBusMenu dependency under `third_party`.
- The tray menu is intentionally simple: Open BusyMax, Agenda, and Quit.
- The tray Agenda action opens the compact Agenda utility window. On GNOME
Wayland this window is a normal top-level utility window and may be placed by
Mutter instead of under the tray icon. Under X11/XWayland, BusyMax requests a
top-right position when the backend supports absolute movement.
- Settings, task data, and token metadata are stored inside the snap user data
sandbox. Normal app restarts preserve data. Removing the snap removes user
data unless snapd creates and restores a snapshot.
- OAuth uses the system browser and a local loopback callback listener.
- OAuth tokens use the XDG Secret portal in the snap to retrieve a
per-application encryption secret, then store only AES-GCM ciphertext under
`XDG_DATA_HOME`. BusyMax must not require the `password-manager-service`
interface.

## Validation Matrix

Record the exact desktop environments tested before upload:

- Ubuntu GNOME on Wayland: pending local installed-snap validation.
- Ubuntu GNOME on X11/XWayland: pending local installed-snap validation.

Required smoke checks before upload:

- Launch from terminal and desktop launcher.
- Google and Microsoft sign-in open in the browser and complete the loopback
callback.
- Secure token storage survives app restart.
- `grep -R` over snap user data does not show plaintext OAuth tokens.
- Settings and task data survive app restart.
- Notifications appear through the desktop notification service.
- Tray icon appears, the menu opens, Open BusyMax restores the existing main
window, Agenda opens the compact Agenda window, and Quit exits cleanly.
- Compact Agenda data loads through the main-window bridge without opening a
second migrating SQLite connection.

## Reporting Bugs

Report beta issues at https://github.com/busystack/busymax/issues.

Source code is available at https://github.com/busystack/busymax.
Repeat the local smoke checks against the Store-delivered revision.

Official references: [build environments](https://documentation.ubuntu.com/snapcraft/stable/reference/build-environment-options/),
[upload](https://documentation.ubuntu.com/snapcraft/stable/reference/commands/upload/),
and [revision management](https://documentation.ubuntu.com/snapcraft/stable/how-to/publishing/manage-revisions-and-releases/).
17 changes: 12 additions & 5 deletions lib/src/platform/main_window_command_bridge.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ import '../features/sync/sync_auth_error.dart';
import '../schedule/schedule_commands.dart';
import 'main_window_command_client.dart';

Future<Map<String, Object?>> loadFreshCompactAgendaSnapshot(
WidgetRef ref,
Object? rawArgs,
) async {
final query = decodeCompactAgendaQuery(rawArgs);
final data = await ref.refresh(
compactAgendaDataForQueryProvider(query).future,
);
return encodeCompactAgendaData(data);
}

class MainWindowCommandBridge extends ConsumerStatefulWidget {
const MainWindowCommandBridge({super.key, required this.child});

Expand Down Expand Up @@ -117,11 +128,7 @@ class _MainWindowCommandBridgeState
}

Future<Map<String, Object?>> _compactAgendaSnapshot(Object? rawArgs) async {
final query = decodeCompactAgendaQuery(rawArgs);
final data = await ref.read(
compactAgendaDataForQueryProvider(query).future,
);
return encodeCompactAgendaData(data);
return loadFreshCompactAgendaSnapshot(ref, rawArgs);
}

Future<bool> _requestTaskSync(Object? rawArgs) async {
Expand Down
5 changes: 5 additions & 0 deletions linux/io.busystack.busymax.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
</screenshots>
<content_rating type="oars-1.1" />
<releases>
<release version="0.1.2" date="2026-07-21" type="development">
<description>
<p>Beta maintenance release.</p>
</description>
</release>
<release version="0.1.1" date="2026-07-19" type="development">
<description>
<p>Beta maintenance release.</p>
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: busymax
description: BusyMax calendar and task manager.
publish_to: 'none'
version: 0.1.1
version: 0.1.2

environment:
sdk: ^3.12.0
Expand Down
2 changes: 1 addition & 1 deletion snap/snapcraft.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: busymax
title: BusyMax
version: "0.1.1"
version: "0.1.2"
summary: Calendar and task manager
description: |
BusyMax is a Linux desktop calendar and task manager.
Expand Down
Loading
Loading