Backup bows out of the phone — the zipped-share future replaces it, later
IndieBackup comes out whole: the package, the Backup directory, the Settings section, the .kanbanbackup document type and its onOpenURL restore path. Settings is now the About section alone. The Files-app keys stay — they serve local-board visibility, not backups. Board sharing as a zipped archive is the planned replacement and is deliberately not started here. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -1,9 +1,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import IndieBackup
|
|
||||||
import os
|
|
||||||
|
|
||||||
/// The iPhone app's entry point. Two tabs, per the mobile MVP charter: Boards (the whole
|
/// The iPhone app's entry point. Two tabs, per the mobile MVP charter: Boards (the whole
|
||||||
/// board → lane → card navigation stack) and Settings (backup/restore).
|
/// board → lane → card navigation stack) and Settings.
|
||||||
@main
|
@main
|
||||||
struct MobileApp: App {
|
struct MobileApp: App {
|
||||||
|
|
||||||
@@ -13,40 +11,10 @@ struct MobileApp: App {
|
|||||||
/// constructing one.
|
/// constructing one.
|
||||||
@State private var boardIndex = BoardIndexStore()
|
@State private var boardIndex = BoardIndexStore()
|
||||||
|
|
||||||
/// Backup/restore (indie-backup skill), built once `boardIndex.home` resolves to a real
|
|
||||||
/// `CloudHome` — see `MobileBackupConfiguration`'s doc comment for why this can't happen at
|
|
||||||
/// `@State` init time. Stays `nil` for the lifetime of a run with no iCloud account;
|
|
||||||
/// `SettingsTabView` renders the quiet disabled state for exactly that case.
|
|
||||||
@State private var backupController: BackupController?
|
|
||||||
|
|
||||||
private static let logger = Logger(subsystem: "dev.rzen.indie.KanbanMobile", category: "backup")
|
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
RootTabView()
|
RootTabView()
|
||||||
.environment(boardIndex)
|
.environment(boardIndex)
|
||||||
.environment(\.backupController, backupController)
|
|
||||||
.task(id: boardIndex.home) {
|
|
||||||
guard backupController == nil, let home = boardIndex.home else { return }
|
|
||||||
backupController = BackupController(
|
|
||||||
configuration: MobileBackupConfiguration(root: home.documentsURL, index: boardIndex)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.onOpenURL { url in
|
|
||||||
// A backup file opened from Files, AirDrop, or a share sheet. Silently
|
|
||||||
// ignored before `backupController` exists — there is no resolved cloud home
|
|
||||||
// to restore into either, at that point.
|
|
||||||
guard url.pathExtension == MobileBackupConfiguration.fileExtension,
|
|
||||||
let backupController
|
|
||||||
else { return }
|
|
||||||
Task {
|
|
||||||
do {
|
|
||||||
try await backupController.restoreBackup(from: url)
|
|
||||||
} catch {
|
|
||||||
Self.logger.error("restore from opened file failed: \(error.localizedDescription, privacy: .public)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import IndieBackup
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
/// IndieBackup's app-specific wiring (indie-backup skill): what gets backed up, and how the
|
|
||||||
/// app's in-memory board list is rebuilt after a restore.
|
|
||||||
///
|
|
||||||
/// **Why this type is constructed rather than defaulted.** `BackupConfiguration.backupRoot` is a
|
|
||||||
/// non-optional `URL`, but the ubiquity container resolves asynchronously and can fail outright
|
|
||||||
/// (`CloudHomeResolver`, CloudHome.swift) — there is no root to hand IndieBackup until
|
|
||||||
/// `BoardIndexStore.home` exists. Rather than answer with a placeholder path, this type is only
|
|
||||||
/// ever constructed once a real `CloudHome` is in hand (`MobileApp`'s `.task(id:)`); before that
|
|
||||||
/// there is no `BackupController` at all, and `SettingsTabView` renders a quiet disabled
|
|
||||||
/// explanation instead of a section built on a fabricated root.
|
|
||||||
final class MobileBackupConfiguration: BackupConfiguration {
|
|
||||||
/// No dot. Shared by the Info.plist document-type/UTI registration
|
|
||||||
/// (`dev.rzen.indie.kanban-backup`) and the archive filenames IndieBackup writes.
|
|
||||||
static let fileExtension = "kanbanbackup"
|
|
||||||
|
|
||||||
private let root: URL
|
|
||||||
private let index: BoardIndexStore
|
|
||||||
|
|
||||||
/// - Parameters:
|
|
||||||
/// - root: The resolved `CloudHome.documentsURL` — the same `Documents/` folder every
|
|
||||||
/// `.kanban` board lives directly inside. IndieBackup walks it file-by-file (it has no
|
|
||||||
/// notion of "package"), so a board's directory structure round-trips through a backup
|
|
||||||
/// as an ordinary tree of files — no directory registration or special-casing needed.
|
|
||||||
/// - index: Rescanned after a restore replaces the tree, in `rebuildCacheAfterRestore`.
|
|
||||||
init(root: URL, index: BoardIndexStore) {
|
|
||||||
self.root = root
|
|
||||||
self.index = index
|
|
||||||
}
|
|
||||||
|
|
||||||
var backupFileExtension: String { Self.fileExtension }
|
|
||||||
var backupDisplayName: String { "Lanework Backup" }
|
|
||||||
var backupRoot: URL { root }
|
|
||||||
|
|
||||||
/// `BoardIndexStore` caches nothing durable — no Core Data, no SwiftData, no file on disk —
|
|
||||||
/// only an in-memory `[BoardSummary]` scanned straight from the files under `backupRoot`. A
|
|
||||||
/// restore already replaced that tree by the time this runs, so the only rebuild step is
|
|
||||||
/// asking the store to rescan it; a `BoardSession` for a board that's open elsewhere reloads
|
|
||||||
/// its own content from disk independently the next time it's asked (BoardIndexStore's doc
|
|
||||||
/// comment on the observer flow).
|
|
||||||
///
|
|
||||||
/// `refresh()` only *starts* the rescan (it hands off to a detached `Task` and returns), so
|
|
||||||
/// this reports done as soon as the request lands, not once the boards tab has repainted —
|
|
||||||
/// same as any other call to `refresh()` in this app (e.g. pull-to-refresh).
|
|
||||||
func rebuildCacheAfterRestore(progress: @escaping (Double, String) -> Void) async throws {
|
|
||||||
progress(0, "Rescanning boards…")
|
|
||||||
await index.refresh()
|
|
||||||
progress(1, "Rescanning boards…")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Threads the lazily-constructed `BackupController` from `MobileApp` down to `SettingsTabView`
|
|
||||||
/// (the backup section) and back up to `MobileApp`'s own `onOpenURL` handler (imported backup
|
|
||||||
/// files), without forcing `RootTabView` — or anything else between them — to know backup exists.
|
|
||||||
/// `nil` until the cloud home resolves; see `MobileBackupConfiguration`.
|
|
||||||
private struct BackupControllerKey: EnvironmentKey {
|
|
||||||
static let defaultValue: BackupController? = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
extension EnvironmentValues {
|
|
||||||
var backupController: BackupController? {
|
|
||||||
get { self[BackupControllerKey.self] }
|
|
||||||
set { self[BackupControllerKey.self] = newValue }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,5 +3,3 @@
|
|||||||
Version 1.0: Lanework comes to iPhone — browse your boards from iCloud Drive, move cards between lanes, and edit them on the go.
|
Version 1.0: Lanework comes to iPhone — browse your boards from iCloud Drive, move cards between lanes, and edit them on the go.
|
||||||
|
|
||||||
Swipe a board in the list to open its settings and move it between iCloud and this iPhone, which now works without an iCloud account.
|
Swipe a board in the list to open its settings and move it between iCloud and this iPhone, which now works without an iCloud account.
|
||||||
|
|
||||||
Back up your boards from Settings and restore any backup later, even on a new phone.
|
|
||||||
|
|||||||
+2
-35
@@ -28,9 +28,8 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
</array>
|
</array>
|
||||||
<!-- Document-based, so App Store Connect requires this (indie-backup skill ▸ Register the
|
<!-- Document-based, so App Store Connect requires this — the in-place editing that lets a
|
||||||
Backup File Type) — the in-place editing the imported-backup `onOpenURL` handler
|
board opened from the Files app (or "On My iPhone ▸ Lanework") be edited where it sits. -->
|
||||||
(MobileApp.swift) relies on. -->
|
|
||||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
<true/>
|
<true/>
|
||||||
<!-- Publishes the sandbox's Documents/ as "On My iPhone ▸ Lanework" in the Files app — the
|
<!-- Publishes the sandbox's Documents/ as "On My iPhone ▸ Lanework" in the Files app — the
|
||||||
@@ -59,21 +58,6 @@
|
|||||||
<key>LSTypeIsPackage</key>
|
<key>LSTypeIsPackage</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
<!-- The backup archive IndieBackup writes/reads (indie-backup skill), so Files, AirDrop
|
|
||||||
and share sheets route a tapped `.kanbanbackup` file to this app's `onOpenURL`
|
|
||||||
instead of leaving it unopenable. -->
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleTypeName</key>
|
|
||||||
<string>Lanework Backup</string>
|
|
||||||
<key>CFBundleTypeRole</key>
|
|
||||||
<string>Editor</string>
|
|
||||||
<key>LSHandlerRank</key>
|
|
||||||
<string>Owner</string>
|
|
||||||
<key>LSItemContentTypes</key>
|
|
||||||
<array>
|
|
||||||
<string>dev.rzen.indie.kanban-backup</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
</array>
|
||||||
<key>UTExportedTypeDeclarations</key>
|
<key>UTExportedTypeDeclarations</key>
|
||||||
<array>
|
<array>
|
||||||
@@ -93,23 +77,6 @@
|
|||||||
<string>kanban</string>
|
<string>kanban</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
|
||||||
<key>UTTypeConformsTo</key>
|
|
||||||
<array>
|
|
||||||
<string>public.data</string>
|
|
||||||
</array>
|
|
||||||
<key>UTTypeDescription</key>
|
|
||||||
<string>Lanework Backup</string>
|
|
||||||
<key>UTTypeIdentifier</key>
|
|
||||||
<string>dev.rzen.indie.kanban-backup</string>
|
|
||||||
<key>UTTypeTagSpecification</key>
|
|
||||||
<dict>
|
|
||||||
<key>public.filename-extension</key>
|
|
||||||
<array>
|
|
||||||
<string>kanbanbackup</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</array>
|
</array>
|
||||||
<!-- Publishes the container's Documents/ as a visible folder in iCloud Drive — on every
|
<!-- Publishes the container's Documents/ as a visible folder in iCloud Drive — on every
|
||||||
platform, including the Mac's Finder, which is how boards reach the Mac app before it
|
platform, including the Mac's Finder, which is how boards reach the Mac app before it
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import IndieAbout
|
import IndieAbout
|
||||||
import IndieBackup
|
|
||||||
|
|
||||||
/// Backup/restore (indie-backup skill) and the About section (indie-about skill).
|
/// The About section (indie-about skill).
|
||||||
struct SettingsTabView: View {
|
struct SettingsTabView: View {
|
||||||
@Environment(BoardIndexStore.self) private var index
|
@Environment(BoardIndexStore.self) private var index
|
||||||
@Environment(\.backupController) private var backupController
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
backupSection
|
|
||||||
|
|
||||||
// Last section by convention. The version label doubles as the changelog link,
|
|
||||||
// which is why the changelog sits in its dedicated slot and not in `documents`.
|
|
||||||
// Same voice as the Mac app's `AboutBox.configuration`; the changelog itself is
|
// Same voice as the Mac app's `AboutBox.configuration`; the changelog itself is
|
||||||
// the phone app's own (see project.yml — separate product, separate 1.0).
|
// the phone app's own (see project.yml — separate product, separate 1.0).
|
||||||
Section {
|
Section {
|
||||||
@@ -27,36 +21,7 @@ struct SettingsTabView: View {
|
|||||||
.navigationTitle("Settings")
|
.navigationTitle("Settings")
|
||||||
}
|
}
|
||||||
// Idempotent (BoardIndexStore.start()), and harmless if BoardsTabView already called it —
|
// Idempotent (BoardIndexStore.start()), and harmless if BoardsTabView already called it —
|
||||||
// this tab can be the first one the user opens, and `backupController` only ever appears
|
// this tab can be the first one the user opens.
|
||||||
// once `index.home` resolves.
|
|
||||||
.task { index.start() }
|
.task { index.start() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `backupController` is `nil` until `MobileApp` has a resolved `CloudHome` to build one from
|
|
||||||
/// — which is also exactly the condition under which there is a `Documents/` folder to back
|
|
||||||
/// up. Everything short of that gets the same quiet explanation rather than a section that
|
|
||||||
/// looks broken or, worse, a crash on a nil root.
|
|
||||||
@ViewBuilder
|
|
||||||
private var backupSection: some View {
|
|
||||||
if let backupController {
|
|
||||||
BackupsSectionView(controller: backupController)
|
|
||||||
} else {
|
|
||||||
Section {
|
|
||||||
switch index.phase {
|
|
||||||
case .idle, .loading:
|
|
||||||
LabeledContent("Backup") {
|
|
||||||
ProgressView()
|
|
||||||
}
|
|
||||||
case .ready:
|
|
||||||
// `.ready` is both cases now: the run with no iCloud at all, and the runloop turn
|
|
||||||
// `backupController` lags `index.home` by (MobileApp's `.task(id:)`). Backup is
|
|
||||||
// still iCloud-only — a known gap now that local boards exist.
|
|
||||||
Label("Backup and restore need iCloud Drive.", systemImage: "icloud.slash")
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
} footer: {
|
|
||||||
Text("Sign into iCloud in Settings, then come back here to back up your boards.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,10 @@ final class SettingsAboutUITests: XCTestCase {
|
|||||||
|
|
||||||
app.tabBars.buttons["Settings"].tap()
|
app.tabBars.buttons["Settings"].tap()
|
||||||
|
|
||||||
// The About section is last in the form (`SettingsTabView`'s own comment: "Last section
|
// The About section is the only section in the form — a speculative swipe settles the
|
||||||
// by convention"), below the Backup section — a couple of speculative swipes settle the
|
|
||||||
// list near the bottom before anything here is queried, harmless if the content already
|
// list near the bottom before anything here is queried, harmless if the content already
|
||||||
// fit on screen.
|
// fit on screen.
|
||||||
app.swipeUp()
|
app.swipeUp()
|
||||||
app.swipeUp()
|
|
||||||
|
|
||||||
// `IndieAbout`'s version line is three sibling `Text` views (prefix, the tappable
|
// `IndieAbout`'s version line is three sibling `Text` views (prefix, the tappable
|
||||||
// "1.0-<build>" link, suffix) — none of them wrapped in a control that would flatten them
|
// "1.0-<build>" link, suffix) — none of them wrapped in a control that would flatten them
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ Lanework is in early development. This list tracks what has actually shipped and
|
|||||||
- **Branches and commit identity** — the board popover's git section is where a git board's branches live: the current branch is itself the picker, and beside the local branches it offers create-and-switch, which starts the new branch at the commit you are on. **Switching is never silent.** If any card window is holding unsaved keystrokes, an open Edit session whose crash-safe saves no commit has yet, or an open raw-source buffer, the switch stops and asks — **Save All**, **Discard**, or **Cancel** — for *every* open window, not just the ones the checkout would touch, because an unapplied raw buffer would otherwise write the whole pre-switch file onto the new branch's card later on. Save All ends each session with its normal commit and applies each raw buffer; a buffer that won't validate cancels the whole switch and puts you in front of the window that refused, nothing half-switched. Discard puts both the buffers and their uncommitted on-disk saves back to the last commit. With the sessions settled the pending auto-commit flushes onto the branch you are **leaving**, so the checkout runs on a genuinely settled tree and cannot fail dirty — and the checkout itself is git's *safe* one, never a force: work the app somehow didn't settle refuses the switch rather than being overwritten. The switch is bracketed like every wholesale operation — the watcher suspended, one full reload at the end, an in-progress row saying "Switching to 'main'…" — and if that final reload fails, the board locks read-only until a reload succeeds rather than letting you edit a snapshot of the branch you just left. Undo and redo do not survive the switch: the stack is discarded and reseeded from the new branch's own history, with redo empty, because replaying a restore from the old branch onto the new one would be wrong. When another program holds the repository's index, the switch waits and retries quietly, the row changing to say it is waiting for another writer's git lock and eventually naming the lock file — never a dialog, and the lock is never removed, because it isn't the app's. **An interrupted switch cleans up after itself**: before touching the repository the app writes down what it is about to do — in its own state, never in your board and never inside `.git` — so a crash or an unplugged volume mid-switch is recognized at the next open, rolled back to the state you were in, and announced ("A branch switch was interrupted — the previous state is restored"); an unfinished merge or rebase that *isn't* the app's is still left strictly alone. Beneath the branches sit the **commit-identity** fields: what you type there is written to the repository's own `.git/config`, so the setting is the file — portable to any git client, per board, and what the next commit is signed with. Leave a field empty and it shows the derived default (your account name, and `you@yourmac`) as a placeholder rather than filling it in, because a value the app wrote there would quietly outrank your own global git config for that board. A config edited from outside while the popover is open refreshes the fields you aren't typing in and leaves the one you are alone.
|
- **Branches and commit identity** — the board popover's git section is where a git board's branches live: the current branch is itself the picker, and beside the local branches it offers create-and-switch, which starts the new branch at the commit you are on. **Switching is never silent.** If any card window is holding unsaved keystrokes, an open Edit session whose crash-safe saves no commit has yet, or an open raw-source buffer, the switch stops and asks — **Save All**, **Discard**, or **Cancel** — for *every* open window, not just the ones the checkout would touch, because an unapplied raw buffer would otherwise write the whole pre-switch file onto the new branch's card later on. Save All ends each session with its normal commit and applies each raw buffer; a buffer that won't validate cancels the whole switch and puts you in front of the window that refused, nothing half-switched. Discard puts both the buffers and their uncommitted on-disk saves back to the last commit. With the sessions settled the pending auto-commit flushes onto the branch you are **leaving**, so the checkout runs on a genuinely settled tree and cannot fail dirty — and the checkout itself is git's *safe* one, never a force: work the app somehow didn't settle refuses the switch rather than being overwritten. The switch is bracketed like every wholesale operation — the watcher suspended, one full reload at the end, an in-progress row saying "Switching to 'main'…" — and if that final reload fails, the board locks read-only until a reload succeeds rather than letting you edit a snapshot of the branch you just left. Undo and redo do not survive the switch: the stack is discarded and reseeded from the new branch's own history, with redo empty, because replaying a restore from the old branch onto the new one would be wrong. When another program holds the repository's index, the switch waits and retries quietly, the row changing to say it is waiting for another writer's git lock and eventually naming the lock file — never a dialog, and the lock is never removed, because it isn't the app's. **An interrupted switch cleans up after itself**: before touching the repository the app writes down what it is about to do — in its own state, never in your board and never inside `.git` — so a crash or an unplugged volume mid-switch is recognized at the next open, rolled back to the state you were in, and announced ("A branch switch was interrupted — the previous state is restored"); an unfinished merge or rebase that *isn't* the app's is still left strictly alone. Beneath the branches sit the **commit-identity** fields: what you type there is written to the repository's own `.git/config`, so the setting is the file — portable to any git client, per board, and what the next commit is signed with. Leave a field empty and it shows the derived default (your account name, and `you@yourmac`) as a placeholder rather than filling it in, because a value the app wrote there would quietly outrank your own global git config for that board. A config edited from outside while the popover is open refreshes the fields you aren't typing in and leaves the one you are alone.
|
||||||
- **A card's History** — the card window's sidebar gains a read-only **History** section on git boards: every commit that touched that card, newest first, each row its own semantic subject over a relative date and the author who made it — so an agent's work and your own read as one story ("Move card 'Fix login' to Doing · 2 days ago · Claude"). It follows the card by identity rather than by path, so moving between lanes — or into the trash and back — keeps one continuous trail. Read-only in this version: restoring a single old version stays a git-client job. The section is simply absent on boards without app-managed git — no placeholder, no greyed-out promise.
|
- **A card's History** — the card window's sidebar gains a read-only **History** section on git boards: every commit that touched that card, newest first, each row its own semantic subject over a relative date and the author who made it — so an agent's work and your own read as one story ("Move card 'Fix login' to Doing · 2 days ago · Claude"). It follows the card by identity rather than by path, so moving between lanes — or into the trash and back — keeps one continuous trail. Read-only in this version: restoring a single old version stays a git-client job. The section is simply absent on boards without app-managed git — no placeholder, no greyed-out promise.
|
||||||
|
|
||||||
- **Lanework for iPhone (in development)** — a companion iOS app, second product rather than second edition (`dev.rzen.indie.KanbanMobile`), whose boards are the same `.kanban` folders in an iCloud Drive container (`iCloud.dev.rzen.indie.Kanban` — a folder the Mac's Finder shows as "Lanework", so the Mac app opens the same boards through the ordinary open panel today and can adopt the container natively later with no migration). It compiles the Mac app's storage engine *verbatim* — same loader, same writer, same byte-fidelity contract, so the two apps cannot drift on the format — and drives it from an `NSMetadataQuery` observer instead of FSEvents. The MVP surface: a Boards tab (boards → lanes → cards → card editor, with per-item download/materialization states honestly rendered), swipe actions to move a card between lanes or send it to `.trash/`, a card editor for title, body, and the typed style attributes (icon, icon color, background), and a Settings tab with local backup/restore of the whole container (IndieBackup) plus an About section with the app's own changelog and license (IndieAbout). No git on the phone — the change signal is the container itself.
|
- **Lanework for iPhone (in development)** — a companion iOS app, second product rather than second edition (`dev.rzen.indie.KanbanMobile`), whose boards are the same `.kanban` folders in an iCloud Drive container (`iCloud.dev.rzen.indie.Kanban` — a folder the Mac's Finder shows as "Lanework", so the Mac app opens the same boards through the ordinary open panel today and can adopt the container natively later with no migration). It compiles the Mac app's storage engine *verbatim* — same loader, same writer, same byte-fidelity contract, so the two apps cannot drift on the format — and drives it from an `NSMetadataQuery` observer instead of FSEvents. The MVP surface: a Boards tab (boards → lanes → cards → card editor, with per-item download/materialization states honestly rendered), swipe actions to move a card between lanes or send it to `.trash/`, a card editor for title, body, and the typed style attributes (icon, icon color, background), and a Settings tab with an About section for the app's own changelog and license (IndieAbout). No git on the phone — the change signal is the container itself.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|||||||
-10
@@ -52,15 +52,6 @@ packages:
|
|||||||
libgit2:
|
libgit2:
|
||||||
url: https://github.com/ibrahimcetin/libgit2.git
|
url: https://github.com/ibrahimcetin/libgit2.git
|
||||||
exactVersion: 1.9.2
|
exactVersion: 1.9.2
|
||||||
# Local backup/restore for the phone app's Settings tab (indie-backup skill). `KanbanMobile`
|
|
||||||
# only — see that target's dependencies below; the Mac app has no Settings surface for this
|
|
||||||
# yet. Pinned to `from: "2.0.0"`, the tag the persistence-agnostic API (a single `backupRoot`
|
|
||||||
# plus a rebuild-cache-on-restore callback — here, `BoardIndexStore.refresh()`, no database in
|
|
||||||
# the loop) shipped under; 1.0.x resolves to the older iCloud-required, CloudKit-coupled
|
|
||||||
# surface this app doesn't want.
|
|
||||||
IndieBackup:
|
|
||||||
url: https://git.rzen.dev/rzen/indie-backup.git
|
|
||||||
from: "2.0.0"
|
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
base:
|
base:
|
||||||
@@ -210,7 +201,6 @@ targets:
|
|||||||
# the MVP has no preview surface yet.
|
# the MVP has no preview surface yet.
|
||||||
- package: swift-markdown
|
- package: swift-markdown
|
||||||
product: Markdown
|
product: Markdown
|
||||||
- package: IndieBackup
|
|
||||||
# The Settings tab's About section — same package, same pin as the Mac target above.
|
# The Settings tab's About section — same package, same pin as the Mac target above.
|
||||||
- package: IndieAbout
|
- package: IndieAbout
|
||||||
# Same stamping as the Mac target: CFBundleVersion from git commit count, BuildDate/BuildHash
|
# Same stamping as the Mac target: CFBundleVersion from git commit count, BuildDate/BuildHash
|
||||||
|
|||||||
Reference in New Issue
Block a user