Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
4b95617
feat: add Cmd+N shortcut for sibling notes and improve edit focus
careck May 12, 2026
6828764
chore: update package-lock.json
careck May 12, 2026
c5189fa
docs: update CHANGELOG for sibling shortcut and edit focus improvements
careck May 12, 2026
058befb
feat: auto-focus tree panel when workspace opens
careck May 12, 2026
f75651c
docs: add tree auto-focus to CHANGELOG
careck May 12, 2026
9e3a4ac
fix: restore sibling menu event handler lost during file copy
careck May 12, 2026
6a6bb03
Merge pull request #198 from 2pisoftware/feat/add-sibling-shortcut
careck May 12, 2026
1258abf
fix: sync workspace properties via operation log and gate UI for non-…
careck May 13, 2026
a2ad6c8
Merge remote-tracking branch 'github-https/development' into fix/work…
careck May 13, 2026
f4c6886
fix: resolve merge formatting and add UpdateWorkspaceMetadata to RBAC…
careck May 13, 2026
660ac63
docs: add mobile support design spec
careck Apr 29, 2026
3607ef0
docs: note M2 ARM mitigates SQLCipher emulator risk
careck Apr 29, 2026
790527b
docs: add implementation plans for mobile support
careck Apr 29, 2026
6cbfead
refactor: replace MAC-based device ID with persisted UUID (#172)
careck May 14, 2026
8eaaa3c
chore: initialize Tauri iOS target and fix bundle identifier
careck May 31, 2026
3ff4817
refactor: gate desktop-only code with #[cfg(desktop)]
careck May 31, 2026
99b644f
feat: mobile workspace lifecycle — label fix, event wiring, launcher UI
careck May 31, 2026
b64ead6
feat: phone stack navigation with slide transitions
careck May 31, 2026
98ede4a
fix: prevent iOS auto-zoom on input focus
careck May 31, 2026
bd13779
feat: long-press context menu and add-note button for mobile
careck May 31, 2026
2ed2432
fix: phone layout uses fixed inset-0 to fill viewport edge-to-edge
careck May 31, 2026
c3a6ccf
fix: switch to note screen after creating a note on phone
careck May 31, 2026
9b15a87
fix: stack title and action buttons vertically on phone
careck May 31, 2026
0d8b371
fix: disable text selection on tree nodes to prevent blue overlay on …
careck Jun 1, 2026
4772f33
feat: hamburger menu on phone tree view
careck Jun 1, 2026
4376912
fix: constrain dialog widths on mobile to prevent overflow
careck Jun 1, 2026
0327e0c
fix: make dialogs full-screen on phone to prevent bleed-through
careck Jun 1, 2026
44f52d8
feat: mobile-friendly operations log with expandable cards
careck Jun 1, 2026
371204e
fix: use correct i18n keys for hamburger menu items
careck Jun 1, 2026
c2c6e1f
fix: force WorkspaceView remount on workspace switch
careck Jun 1, 2026
f3db2bf
chore: initialize Tauri Android target
careck Jun 1, 2026
8a2d3a6
fix: use app sandbox data directory on mobile for home_dir
careck Jun 1, 2026
385d277
fix: Android data dir fallback when dirs::data_dir() returns None
careck Jun 1, 2026
886aa48
fix: respect safe area insets on mobile (status bar, notch)
careck Jun 1, 2026
156c033
fix: disable edge-to-edge on Android to keep content below status bar
careck Jun 2, 2026
e4d9f57
fix: apply system bar insets to Android WebView content area
careck Jun 2, 2026
2959dd3
fix: iOS build toolchain and safe area handling
careck Jun 2, 2026
fcaba28
chore: add android-dev.sh convenience script
careck Jun 2, 2026
ebe5c1c
Merge remote-tracking branch 'github-https/development' into mobile
careck Jun 2, 2026
a33e99e
style: cargo fmt
careck Jun 2, 2026
3a4faa7
Merge pull request #207 from 2pisoftware/mobile
careck Jun 2, 2026
5e03364
docs: add mobile support PR #207 to changelog
careck Jun 2, 2026
9ba5aae
docs: add MOBILE_README.md with build and simulator instructions
careck Jun 2, 2026
c1e6d72
fix: replace hardcoded user paths with dynamic rustup resolution in i…
careck Jun 2, 2026
674c951
Merge pull request #199 from 2pisoftware/fix/workspace-props-sync
careck Jun 2, 2026
bd12a83
Merge master into development
careck Jun 2, 2026
a6735b4
Merge branch 'development' of https://github.com/2pisoftware/krillnot…
careck Jun 2, 2026
7907482
feat: mobile note move actions via context menu
careck Jun 2, 2026
4da3c31
fix: increase initial window height to fit launch screen
careck Jun 2, 2026
2fa1a27
fix: use template literals for context menu item classes
careck Jun 2, 2026
bc8e62d
fix: remove default focus outline from tree container
careck Jun 2, 2026
e529b56
fix: remove focus ring from tree container entirely
careck Jun 2, 2026
817912b
Merge pull request #208 from 2pisoftware/feat/mobile-move-notes
careck Jun 2, 2026
1b694a5
chore: change app identifier to io.opswarm.krillnotes
careck Jun 2, 2026
c745e04
fix: auto-start emulator in android-dev.sh --emulator mode
careck Jun 2, 2026
34f5b5e
style: cargo fmt
careck Jun 2, 2026
093a959
Merge pull request #209 from 2pisoftware/feat/mobile-move-notes
careck Jun 2, 2026
4ac4484
chore: bump version to 1.2.0
careck Jun 2, 2026
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.2.0] — 2026-06-02

### Added
- **Mobile support — iOS and Android** — Tauri iOS and Android targets with phone stack navigation (full-screen tree, slide-right to note, back button), hamburger menu, long-press context menu, full-screen dialogs, and adaptive layout via `useLayout()` hook. Safe areas handled natively on both platforms. Convenience scripts `ios-dev.sh` and `android-dev.sh` for simulator/emulator launches (#207).

### Fixed
- **AppImage `.DirIcon` symlink** — After bundling, the release workflow now repacks each AppImage replacing the absolute `.DirIcon` symlink baked by tauri-bundler with a relative one, fixing installation failures in app-manager and similar tools (workaround for tauri-apps/tauri#15110, PR #203).

Expand Down Expand Up @@ -527,6 +532,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Platform-aware menus: macOS app menu, Edit menu with standard shortcuts; Tools menu for Operations Log and Script Manager.
- Cross-platform release workflow via GitHub Actions (macOS, Windows, Linux).

[1.2.0]: https://github.com/2pisoftware/krillnotes/compare/v1.1.1...v1.2.0
[1.1.1]: https://github.com/2pisoftware/krillnotes/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/2pisoftware/krillnotes/compare/v1.0.1...v1.1.0
[1.0.1]: https://github.com/2pisoftware/krillnotes/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/2pisoftware/krillnotes/compare/v0.9.2...v1.0.0
[0.9.2]: https://github.com/2pisoftware/krillnotes/compare/v0.9.1...v0.9.2
[0.9.1]: https://github.com/2pisoftware/krillnotes/compare/v0.9.0...v0.9.1
Expand Down
89 changes: 89 additions & 0 deletions MOBILE_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Mobile Development

Krillnotes runs on iOS and Android via Tauri v2. The desktop codebase is shared — platform-specific code is gated with `#[cfg(desktop)]` / `#[cfg(not(desktop))]` in Rust and the `useLayout()` hook in React.

## Requirements

### Shared (both platforms)

- **Rust via rustup** (not Homebrew) — Homebrew's cargo lacks cross-compilation targets
- **Node.js + npm**
- **Tauri CLI** — installed via `npm install` in `krillnotes-desktop/`

### iOS

- **Xcode** (full install, not just Command Line Tools)
- **iOS simulator runtimes** (installed via Xcode → Settings → Platforms)
- **Rust iOS targets:**
```bash
rustup target add aarch64-apple-ios aarch64-apple-ios-sim
```
- **xcodegen** — `brew install xcodegen` (regenerates Xcode project from `project.yml`)

### Android

- **Android Studio** (provides SDK, NDK, and bundled JDK)
- **Android SDK** — default location: `~/Library/Android/sdk`
- **Android NDK** — installed via Android Studio SDK Manager
- **Rust Android targets:**
```bash
rustup target add aarch64-linux-android
```
- **An AVD (emulator)** — create via Android Studio Device Manager (e.g. Pixel 8 API 35)

## Quick Start

All commands run from `krillnotes-desktop/`:

### iOS Simulator

```bash
./ios-dev.sh # Launches iPhone 17 Pro simulator (default)
./ios-dev.sh 2 # Launches iPad (A16) — pass simulator index
```

The script:
1. Kills any lingering Vite dev server on port 1420
2. Boots the iOS simulator
3. Sets rustup cargo in PATH (required for cross-compilation)
4. Runs `tauri ios dev` which starts Vite + builds Rust + deploys to simulator

On first run, the full Rust cross-compile takes a few minutes. Subsequent runs are incremental.

### Android Emulator

```bash
./android-dev.sh
```

The script:
1. Kills any lingering Vite dev server on port 1420
2. Sets rustup cargo in PATH
3. Configures ANDROID_HOME, NDK_HOME, and JAVA_HOME
4. Runs `tauri android dev` which starts Vite + builds Rust + deploys to emulator

**Note:** Start the Android emulator first (via Android Studio or `emulator -avd <name>`) before running the script.

### Desktop (unchanged)

```bash
npm run tauri dev
```

## Important Notes

- **Always use rustup cargo**, not Homebrew cargo. The scripts handle this automatically. If building manually, prefix with: `PATH="$(dirname $(rustup which cargo)):$PATH"`
- **Port 1420** — Vite dev server runs here. Only one platform can run at a time. Kill the previous before switching.
- **Xcode standalone builds don't work** — `Cmd+R` in Xcode requires the Tauri dev server running. Always launch via `./ios-dev.sh` or `tauri ios dev`.
- **Hot reload** — Vite hot-reloads React/CSS changes on both platforms. Rust changes require a rebuild (the script handles this).
- **`gen/` directories** — `src-tauri/gen/apple/` and `src-tauri/gen/android/` contain platform-specific project files. The iOS `project.yml` has been customized (rustup PATH fix) — don't regenerate without preserving this.

## Troubleshooting

| Problem | Fix |
|---------|-----|
| `can't find crate for core` on iOS | Homebrew cargo is being used. Ensure rustup cargo is first in PATH. |
| `Unable to lookup in current state: Shutdown` | Simulator isn't booted. Run `xcrun simctl boot <UDID>` or use `./ios-dev.sh` which boots automatically. |
| `failed to read missing addr file` | Tauri dev server isn't running. Don't build from Xcode directly — use `./ios-dev.sh`. |
| Port 1420 already in use | `lsof -ti:1420 \| xargs kill -9` |
| Android `JAVA_HOME` not set | Install Android Studio; the script uses its bundled JDK. |
145 changes: 145 additions & 0 deletions docs/superpowers/plans/2026-04-29-device-id-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Plan 1a: Device ID Migration

**Issue:** Child of #171 (Mobile support)
**Branch:** `mobile`
**Spec:** `docs/superpowers/specs/2026-04-29-mobile-support-design.md` § Preliminary: Device ID Migration

## Context

The codebase has three device identification mechanisms:

1. **MAC-based** — `device.rs::get_device_id()` returns `device-<16 hex>` via `mac_address` crate. Called in 21 places across sync/relay commands (invites, swarm, relay_accounts, sync, identity, receive_poll) and export.
2. **Per-identity device UUID** — `identity::ensure_device_uuid(dir)` creates/reads a UUID file in each identity directory. Already persisted, already stable.
3. **Composite** — `{identity_uuid}:{device_uuid}` stored in `workspace_meta.device_id`. Used as the HLC node ID and in `RegisterDevice` operations.

The MAC-based ID (1) caused relay sync bugs due to instability across network interfaces. It also blocks mobile (no MAC access on iOS/Android). The per-identity UUID (2) is already the right approach — we just need to make (1) use the same mechanism and remove the `mac_address` dependency.

## Steps

### Step 1: Create app-level device UUID

**Files:** `krillnotes-core/src/core/device.rs`

Replace `get_device_id()` with two functions:

```rust
/// Read or create the app-level device UUID seed file.
/// File: {data_dir}/device_id (plain text, one UUID).
pub fn get_or_create_seed_device_id(data_dir: &Path) -> Result<String>

/// Full priority chain for resolving device ID for a workspace.
/// 1. workspace_meta has device_id → use it
/// 2. operations table has local device_id → use it, write to workspace_meta + seed file
/// 3. Seed file exists → use it, write to workspace_meta
/// 4. Generate device-{uuid}, write everywhere
pub fn resolve_device_id(conn: &Connection, data_dir: &Path) -> Result<String>
```

`get_or_create_seed_device_id`:
- Read `{data_dir}/device_id` file
- If exists and non-empty → return trimmed contents
- If absent → generate `device-{Uuid::new_v4()}`, write to file, return it

`resolve_device_id`:
- Query `SELECT value FROM workspace_meta WHERE key = 'device_id'`
- If found → return it (already migrated or set during creation)
- If absent → query `SELECT DISTINCT device_id FROM operations LIMIT 1`
- If found → write to `workspace_meta`, also write to seed file, return it
- If absent → call `get_or_create_seed_device_id(data_dir)`, write to `workspace_meta`, return it

Remove all `mac_address` crate usage. The old `get_device_id()` function is deleted.

### Step 2: Update Cargo.toml

**File:** `krillnotes-core/Cargo.toml`

- Remove `mac_address = "1.1"` dependency
- Keep `hostname = "0.3"` (used for human-readable device names, has graceful fallback)

### Step 3: Update call sites in krillnotes-core

**File:** `krillnotes-core/src/core/export.rs`

The only core call site. Currently calls `get_device_id()` during export. Replace with:
- Accept `device_id: &str` as a parameter (passed from the Workspace struct which already holds it)
- Or read from `self.device_id` if export is a Workspace method

### Step 4: Update Workspace::open() and init_core()

**File:** `krillnotes-core/src/core/workspace/mod.rs`

`open()`:
- Add `data_dir: &Path` parameter
- After opening DB, call `resolve_device_id(conn, data_dir)` to get/migrate the device ID
- Store result in `self.device_id`
- The existing composite device_id logic (`{identity_uuid}:{device_uuid}`) remains — `resolve_device_id` returns whatever is in workspace_meta, which is already composite for existing workspaces

`init_core()`:
- Add `data_dir: &Path` parameter
- Use `get_or_create_seed_device_id(data_dir)` when constructing the initial device_id if no identity_dir is provided
- Existing logic for composite `{identity_uuid}:{device_uuid}` format stays as-is

### Step 5: Update 21 call sites in krillnotes-desktop

**Files:** `krillnotes-desktop/src-tauri/src/commands/`
- `invites.rs` (2 calls)
- `swarm.rs` (5 calls)
- `relay_accounts.rs` (3 calls)
- `sync.rs` (5 calls)
- `identity.rs` (1 call)
- `receive_poll.rs` (4 calls)

All these call `get_device_id()` independently. Replace each with:
- Read the device_id from the Workspace instance (already available via AppState) OR
- Call `get_or_create_seed_device_id(&home_dir())` for operations that don't have a workspace context

Pattern: most of these commands already have access to the workspace via `state.workspaces`. Extract `device_id` from the workspace struct instead of computing it fresh each time. This is more correct anyway — the device_id should come from the workspace context, not be independently derived.

For commands that operate before a workspace is open (relay account management, identity operations), use `get_or_create_seed_device_id` with the home_dir path.

### Step 6: Update Workspace::open() callers in lib.rs

**File:** `krillnotes-desktop/src-tauri/src/lib.rs` (and commands/workspace.rs)

All calls to `Workspace::open()` and `Workspace::init_core()` need the new `data_dir` parameter. Pass `&home_dir()` on desktop. On mobile (future), the Tauri app will pass the app sandbox directory.

### Step 7: Migration gate in create_workspace command

**File:** `krillnotes-desktop/src-tauri/src/commands/workspace.rs`

In `create_workspace` command:
- Check if seed file exists at `home_dir()/device_id`
- If not → call `list_workspace_files()` (already exists in this file)
- If workspaces exist → return error: "Please open an existing workspace first to migrate your device identity"
- If no workspaces → proceed (fresh install, generate new UUID)

### Step 8: Update tests

**File:** `krillnotes-core/src/core/device.rs` (test module)

- Remove MAC-based tests
- Add tests for `get_or_create_seed_device_id`: creates file on first call, reads same value on second call
- Add tests for `resolve_device_id`: priority chain (workspace_meta > operations > seed file > generate)
- Use temp directories for test isolation

**Files:** Any existing tests that call `get_device_id()` — update to use new API.

### Step 9: Verify and test

- `cargo test -p krillnotes-core` — all tests pass
- `cd krillnotes-desktop && npx tsc --noEmit` — type check
- `cd krillnotes-desktop && npm run tauri dev` — manual test:
- Open existing workspace → device_id migrated to seed file
- Create new workspace → picks up seed file device_id
- Check relay sync still works with migrated ID

## Commit sequence

1. `refactor: replace MAC-based device ID with persisted UUID` — steps 1-6
2. `feat: add migration gate for device ID on new workspace creation` — step 7
3. `test: add device ID migration tests` — step 8

## Risks

- **Relay identity break:** If the old MAC-based `get_device_id()` value was stored on the relay as the device identifier, switching to a UUID means the relay won't recognize the device. However, the MAC-based ID was already unstable (the problem we're fixing), so relay identity was already unreliable. The composite device_id in workspace_meta (which IS stable) is what matters for workspace sync.
- **Export compatibility:** Exported archives embed a device_id. Old exports have MAC-based IDs. Import should handle both formats (no format validation on the device_id string).
Loading
Loading