or family plan) and wants to track shared expenses with 2 to ~8 other people
they trust (e.g. flatmates, partner, travel companions).
Larger groups are not a goal and may exhibit degraded sync performance.
folder inside their connected shared folder provider. The app shall write an
initial ledger metadata file into that folder.
-
3.1.3. Ledger metadata SC-FR-LED-3 A ledger shall expose a single plaintext metadata file in its folder. The
metadata file shall declare at minimum:
- The ledger UUID
- The schema version
- The creation timestamp (UTC)
- The encryption marker "encrypted": true (see SC-ARC-ENC-1)
- The key fingerprint (see SC-ARC-ENC-3)
The metadata file shall NOT contain the ledger's human-readable name, its
participant list, label list, or any other field that could leak sensitive
information about the ledger's contents to anyone who can read the folder
but does not hold the data key. The ledger name and the participant /
label state shall instead be reconstructed from the encrypted event log
(see SC-ARC-LOG-2).
already contains SplitClone ledger metadata. The app shall validate the
metadata and refuse to open folders that are not recognised SplitClone ledgers.
-
3.1.1. Create a new ledger SC-FR-LED-1 The app shall let a user create a new ledger by selecting an empty (or new)
folder inside their connected shared folder provider. The app shall write an
initial ledger metadata file into that folder.
metadata file shall declare at minimum:
- The ledger UUID
- The schema version
- The creation timestamp (UTC)
- The encryption marker "encrypted": true (see SC-ARC-ENC-1)
- The key fingerprint (see SC-ARC-ENC-3)
The metadata file shall NOT contain the ledger's human-readable name, its
participant list, label list, or any other field that could leak sensitive
information about the ledger's contents to anyone who can read the folder
but does not hold the data key. The ledger name and the participant /
label state shall instead be reconstructed from the encrypted event log
(see SC-ARC-LOG-2).
device. Switching ledgers shall be possible but is not a frequent operation.
to that ledger by providing a display name. The system shall assign a stable
participant UUID.
let them either (a) claim an existing unclaimed participant entry, (b) create
a new participant and claim it, or (c) re-claim a participant that is already
claimed on another device, when the same person is adding a further device
(e.g. PC + phone). A device shall be bound to exactly one participant per
ledger; a participant may be bound to more than one device. Claiming moves
the claiming device only — other devices already bound to a participant are
unaffected. The claim screen shall present (c) candidates separately from (a)
and make clear that picking one links the new device to the same person
rather than creating a duplicate.
remain unchanged so existing expenses are not orphaned.
a flatmate who is included in splits but does not own an iPhone). Such
participants can be added by any other user and never claim a device.
- Title (free-form string, max 200 chars)
- Amount (decimal with 2 fractional digits, > 0)
- Execution date (calendar date when the expense actually occurred, defaulting to today; user-editable)
- Payer (exactly one participant)
- Participants in the split (one or more participants, defaulting to all)
- Labels (zero or more, see section Labels)
The system shall additionally and automatically assign:
- A stable expense UUID
- An entry timestamp recording the wall-clock instant at which the expense was recorded in the app (not user-editable)
The execution date and the entry timestamp shall be stored as two distinct
fields. Example: ice cream bought on 2026-04-22 but entered in the app on
2026-04-28T15:00:00Z yields execution date 2026-04-22 and entry timestamp
2026-04-28T15:00:00Z.
and stored in ledger metadata. Per-expense currency override is out of scope.
shall be displayed but does not participate in any calculation.
overwrite the original record in place; it shall be stored as a new event
that supersedes the previous version of the expense (see SC-ARC-LOG-1).
tombstone event so that other devices converge on the deletion when they sync.
ledger state and is therefore visible to all users of that ledger. Labels are
not shared across ledgers.
(free-form string, max 40 chars, case-insensitive uniqueness within the
ledger). The system shall assign the label a stable UUID.
unchanged so that existing expense-to-label assignments are preserved.
recorded as a tombstone event. Expenses that reference a deleted label
shall continue to exist; the dangling reference shall be silently elided
from the derived state so the label simply no longer appears on those
expenses.
time and shall be able to add or remove labels via editing an existing
expense (see SC-FR-EXP-4). Label assignments are stored as part of the
expense record.
in the current ledger and for creating, renaming, and deleting labels from
that screen. The same screen shall show, for each label, the number of
expenses currently carrying it.
participants in the split, each participant's share shall be A/N rounded to
the smallest unit of the ledger currency.
remainder shall be assigned deterministically to the payer so that the sum of
shares always equals the expense amount exactly.
user may exclude the payer at expense entry time, in which case the payer is
fully reimbursed by the other participants.
amount A owes B based on all non-deleted expenses and settlements in the
ledger. The computation shall be a pure function of the event log.
summary listing for each other participant whether the current participant
owes or is owed and how much.
to another, with an amount and a date. A settlement shall reduce the recorded
balance between the two participants accordingly.
re-routing of payments through third parties is out of scope.
newest first, showing at minimum: date, title, amount, payer, and split size.
the split, the amount each owes, the note (if any), the creator, and the
creation timestamp.
participant (as payer or as split member).
labels are selected, the list shall show expenses carrying any of the
selected labels (logical OR). The label filter shall be combinable with the
participant filter (logical AND between filter dimensions).
either bound optional). The filter shall apply to the execution date
(SC-FR-EXP-1), not the entry timestamp.
chosen participant's transactions. The export shall reflect only that
participant's individual money movements (see SC-FR-EXR-3), not the raw
shared expense list.
claimed by the current device (SC-FR-PRT-2). The user may override this
selection and export on behalf of any participant in the ledger.
selected participant P. Both modes share the same CSV schema (see
SC-FR-EXR-4) and the same sign convention (positive = inflow to P / debit
to P's external account that mirrors the export; negative = outflow /
credit). Deleted expenses and deleted settlements shall be excluded in both
modes.
Mode A - Cash basis:
Contains only real money movements involving P. Intended to be reconciled
against P's real bank or card account in the external finance app.
- Expense E paid by P: one row, amount = -E.amount, date = E.execution date.
- Expense E paid by someone else, P in the split: NOT exported.
- Settlement S where P paid: one row, amount = -S.amount, date = S.date.
- Settlement S where P received: one row, amount = +S.amount, date = S.date.
Mode B - Virtual account (share basis):
Intended to be imported into a dedicated "virtual" sub-account in the
external finance app, where the running balance represents P's net position
inside the shared ledger.
- Expense E in which P is the payer with share s out of total amount T:
one row, amount = +(T - s), date = E.execution date. The row is omitted when T = s (P is the sole participant in the split).
- Expense E paid by someone else, P in the split with share s: one row,
amount = -s, date = E.execution date.
- Expense E paid by someone else, P not in the split: NOT exported.
- Settlement S where P paid amount A: one row, amount = +A, date = S.date.
- Settlement S where P received amount A: one row, amount = -A, date = S.date.
The two modes are mutually exclusive within a single export: each invocation
of the export action produces exactly one file in exactly one mode.
the following columns, in this order:
1. Date - ISO-8601 calendar date (YYYY-MM-DD)
2. Description - Expense title for expense rows; "Settlement to <name>" or "Settlement from <name>" for settlement rows
3. Amount - Signed decimal with two fractional digits, period decimal separator, no thousands separator. Positive = inflow to selected participant, negative = outflow.
4. Currency - ISO 4217 currency code, taken from the ledger (SC-FR-EXP-2)
5. Counterparty - For expense rows: comma-separated display names of the OTHER participants in the split (i.e. those who owe a share). For settlement rows: the display name of the other participant.
6. Labels - Semicolon-separated list of label display names attached to the source expense; empty for settlement rows.
7. Note - The expense note (SC-FR-EXP-3), with embedded CR/LF characters replaced by single spaces; empty for settlement rows.
8. ExpenseUUID - The stable expense or settlement UUID, so re-exports can be deduplicated by the importing tool.
Field values shall be RFC 4180 quoted (double quotes, doubled internally)
when they contain commas, double quotes, or newlines.
label filter (SC-FR-HIS-4) that is active in the UI at the moment the export
is initiated, so the user can scope an export to a single month, trip, or
label set.
download triggered from a Blob object and an anchor element carrying the
"download" attribute. Where the Web Share API with file support is
available (notably iOS Safari 15+), the app may additionally offer "Share
via..." to route the file into the OS share sheet (Mail, Files,
third-party apps).
The default filename shall follow the pattern
"splitclone_<ledger-slug>_<participant-slug>_<mode>_<YYYYMMDD-HHMMSS>.csv",
where <mode> is "cash" for Mode A and "virtual" for Mode B (see
SC-FR-EXR-3). Including the mode in the filename prevents users from
accidentally importing the wrong projection into the wrong account.
statements (CSV, MT940, CAMT.053), or other SplitClone instances. Data flow
between SplitClone and other tools is one-way (export only) in the MVP.
opened or brought to the foreground, and additionally at a periodic
interval while it remains in the foreground and connected, so that a
peer's change appears without any manual action. It shall push local
changes to the shared folder within 10 seconds of the user committing
them, network permitting. Periodic polling shall pause while the app is
hidden or offline (the backend offers no push channel, so a bounded poll
is the propagation mechanism; the interval is a tunable constant balancing
freshness against request volume and battery, SRS Q2).
pull and push cycle.
syncing, offline, or sync error (with the error reason).
complete local application database needed to reconstruct app state on
another device: every ledger's full event log, its data key in join-code
form, key fingerprint, optional sync hint and recovery-acknowledged flag.
Export is whole-database, not per-ledger; it is distinct from the
per-ledger CSV export (SC-FR-EXR-*), which is a lossy accounting report.
A ledger whose data key is not present on this device cannot be restored
and shall be omitted from the file, with the user informed.
restore: (a) merge - union the selected ledgers into existing state, which
shall be idempotent (re-importing the same file changes nothing) and shall
never overwrite an existing ledger whose id matches but whose key differs
(key conflict is reported and skipped); or (b) replace - wipe the local
database, then restore the selected ledgers (clean device migration). The
user shall be able to select which ledgers in the file to restore.
(JSON). It contains every ledger's join code (data key) in clear text;
anyone holding the file can decrypt every ledger in it. This is an
explicit, accepted exception to SC-ARC-ENC-*. Securing the exported file
is the user's responsibility, not the app's. The app shall state this
prominently at export time and require an explicit acknowledgement before
producing the file, and shall repeat the warning inside the file itself.
The app shall never transmit the backup to any network endpoint.
event-schema version its contents use. On restore the app shall refuse
loudly (no partial or best-effort import) if either exceeds the version
the app understands, telling the user to update the app; older versions
shall be accepted. Every event in the file shall be re-validated through
the canonical decoder before any data is written; any failure aborts the
whole restore. (Mirrors SC-ARC-FMT-2 for item (g).)
shall never adopt the identity of the device that wrote the backup
(SC-ARC-IDN-1, SC-ARC-LOG-1 sole-writer). Restored events retain their
original authorship in history; new events append under the restoring
device. After a restore the user re-claims their participant per ledger
via the normal multi-device claim flow (SC-FR-PRT-2 (c)).
"SharedFolderProvider" interface. The interface shall be defined narrowly
enough that multiple OneDrive back-ends (Microsoft Graph API; iOS Files /
Document Picker) and future non-OneDrive back-ends (iCloud Drive, Dropbox,
etc.) can coexist as independent implementations selectable at runtime,
with no changes required in the rest of the app when a new back-end is
added.
-
4.2. Microsoft Graph OneDrive provider (MVP) SC-ARC-PRV-3 The MVP shall ship exactly one SharedFolderProvider implementation: a
Microsoft Graph API provider that authenticates the user via OAuth 2.0
(Authorization Code with PKCE) and accesses the ledger folder directly
through the Graph REST API. ETags returned by Graph shall be used to
implement the conditional-write precondition in SC-ARC-PRV-2.
The iOS Files / Document Picker provider that was considered alongside
Graph API in earlier drafts is not buildable in a web app and is
explicitly removed from MVP scope.
-
4.1. Shared folder provider abstraction SC-ARC-PRV-1 All access to the shared storage shall go through an internal
"SharedFolderProvider" interface. The interface shall be defined narrowly
enough that multiple OneDrive back-ends (Microsoft Graph API; iOS Files /
Document Picker) and future non-OneDrive back-ends (iCloud Drive, Dropbox,
etc.) can coexist as independent implementations selectable at runtime,
with no changes required in the rest of the app when a new back-end is
added.
Microsoft Graph API provider that authenticates the user via OAuth 2.0
(Authorization Code with PKCE) and accesses the ledger folder directly
through the Graph REST API. ETags returned by Graph shall be used to
implement the conditional-write precondition in SC-ARC-PRV-2.
The iOS Files / Document Picker provider that was considered alongside
Graph API in earlier drafts is not buildable in a web app and is
explicitly removed from MVP scope.
- List files in the ledger folder (with last-modified timestamps and ETags or equivalent)
- Read a file by name
- Create or replace a file by name, with an optional precondition (ETag / If-Match)
- Delete a file by name
All operations shall be asynchronous and shall report transport errors
distinctly from semantic errors.
per-device, append-only event logs stored in the ledger folder. Each device
shall write only to log files that it owns (named with that device's UUID,
see SC-ARC-LOG-4). No device shall ever write to another device's log
files.
- LedgerRenamed (changes the ledger's human-readable name; sensitive content lives here, not in the plaintext metadata file - see SC-FR-LED-3)
- ParticipantAdded
- ParticipantRenamed
- ParticipantClaimed (binds a device UUID to a participant UUID)
- LabelCreated
- LabelRenamed
- LabelDeleted (tombstone)
- ExpenseCreated (payload includes execution date, entry timestamp, and label UUID list)
- ExpenseUpdated (carries a full new version of the expense, including its label UUID list)
- ExpenseDeleted (tombstone)
- SettlementRecorded
- SettlementUpdated (carries a full new version of the settlement)
- SettlementDeleted (tombstone)
Each event shall carry: event UUID, event type, author device UUID, author
participant UUID, wall-clock timestamp (which is the entry timestamp for
expense events), schema version, and a type-specific payload.
per line, no trailing comma, newline-terminated). Files shall be
append-only: existing lines shall never be modified or removed, only new
lines appended. No compression shall be applied; segments are stored as
plain UTF-8 text.
the ledger folder. Segment files shall be named
"events/<device-uuid>/<segment-open-timestamp>.jsonl", where the timestamp
is the UTC instant the segment was opened, formatted as
"YYYYMMDDTHHMMSSsss" (millisecond precision) so that lexicographic file
ordering matches chronological order.
A device shall have at most one "open" segment per ledger at any time; all
other segments belonging to that device are "closed" and immutable. New
events are appended to the open segment. When the open segment's size on
disk would exceed the segment size threshold (SC-ARC-LOG-5) after writing
the next event, the device shall:
1. Close the current segment (no further writes to it, ever).
2. Open a new segment with the current UTC timestamp as its filename.
3. Append the new event to the new segment.
A device's full event log is reconstructed by reading every segment file
under that device's folder, sorted by filename. The concatenation is the
device's complete, append-only history.
-
4.8. Segment size threshold SC-ARC-LOG-5 The segment size threshold shall be 1 MiB (1,048,576 bytes) for the MVP.
This value is a configurable constant in the implementation, not a wire
format invariant: a device that uses a different threshold remains fully
interoperable with devices using the MVP value, because every device folds
all segments regardless of how their boundaries were drawn.
-
4.18. Authenticated encryption of event-log segments SC-ARC-ENC-2 Every event-log segment file (SC-ARC-LOG-4) shall be encrypted with
AES-256-GCM using the ledger's data key. The on-disk file format for an
encrypted segment shall be:
- 12 bytes: a freshly-generated random IV (nonce)
- N bytes: the GCM ciphertext, where N equals the plaintext JSONL length
- 16 bytes: the GCM authentication tag
A fresh IV shall be generated for every encryption operation, including
every re-upload of the same logical segment after an append. Random 96-bit
IVs are acceptable because the collision probability over the lifetime of
any realistic ledger is negligible.
Decryption shall fail loudly on auth-tag mismatch. A segment that fails
to decrypt shall be reported as an error to the user, never silently
skipped, because it indicates either a wrong key, a corrupted file, or
tampering.
No plaintext segment file shall ever leave the device.
-
4.7. Segmented per-device logs SC-ARC-LOG-4 Each device's event log shall be split across one or more segment files in
the ledger folder. Segment files shall be named
"events/<device-uuid>/<segment-open-timestamp>.jsonl", where the timestamp
is the UTC instant the segment was opened, formatted as
"YYYYMMDDTHHMMSSsss" (millisecond precision) so that lexicographic file
ordering matches chronological order.
A device shall have at most one "open" segment per ledger at any time; all
other segments belonging to that device are "closed" and immutable. New
events are appended to the open segment. When the open segment's size on
disk would exceed the segment size threshold (SC-ARC-LOG-5) after writing
the next event, the device shall:
1. Close the current segment (no further writes to it, ever).
2. Open a new segment with the current UTC timestamp as its filename.
3. Append the new event to the new segment.
A device's full event log is reconstructed by reading every segment file
under that device's folder, sorted by filename. The concatenation is the
device's complete, append-only history.
This value is a configurable constant in the implementation, not a wire
format invariant: a device that uses a different threshold remains fully
interoperable with devices using the MVP value, because every device folds
all segments regardless of how their boundaries were drawn.
compression that would reduce or transform historical event data:
- Closed segments shall be retained verbatim for the lifetime of the
ledger. They shall never be merged, rewritten, or deleted by the app.
- Tombstone events (ExpenseDeleted, SettlementDeleted, LabelDeleted)
shall remain in the log alongside the events they tombstone.
- No segment shall be compressed (gzip, zstd, etc.) at rest.
- The derived-state cache (SC-ARC-CCH-1) is a performance optimisation
only; it shall be reconstructible at any time by folding all segments
from scratch.
The full event history is the source of truth and shall remain forensically
reconstructible from the segment files alone.
a deterministic order (events sorted by wall-clock timestamp, breaking ties
by event UUID lexicographic order). Given the same set of event log files,
all devices shall compute the same derived state.
expense, the event with the later wall-clock timestamp shall win in the
derived state. Ties shall be broken by event UUID lexicographic order. The
loser's event shall remain in the log for auditability.
of the per-segment read offsets (or hashes) used during the last fold.
Re-syncing shall only re-read segments whose remote state has changed, and
within a changed segment shall only fold the new tail. Closed segments
(SC-ARC-LOG-4) are immutable, so once their hash has been recorded they
shall not be re-fetched again.
it in browser IndexedDB under the app's origin. This UUID shall be the
device's identity across all ledgers and shall not leave the device
except as the directory name of that device's log-segment folder inside
each shared OneDrive folder (SC-ARC-LOG-4).
If the IndexedDB record is lost (browser data cleared, private-browsing
session, iOS Safari long-disuse eviction, fresh browser install), a new
device UUID shall be generated on the next launch. The user shall be
prompted to claim a participant identity again as if it were a new device
(SC-FR-PRT-2). The previous device's log segments in the shared folder
remain valid history and shall continue to contribute to the derived
state; they simply receive no further appends.
A new device id does not have to mean a new participant: the same person
re-running the app (after eviction, or simply on an additional device)
re-claims their existing participant via SC-FR-PRT-2 (c). The participant
then has both device ids bound to it; balances and "you" attribution stay
on the one identity. Authorship is still per device id (SC-ARC-IDN-2);
only the participant↔device binding is many-to-one.
(e.g. OneDrive user account) to identify the author of an event. Authorship
shall be established entirely by the device UUID and the claimed participant
UUID inside the event payload.
shall be distributed as immutable static assets from a third-party static
hosting provider. The current target is GitHub Pages; equivalent
alternatives such as Cloudflare Pages, Netlify, or any S3-equivalent
object store are acceptable substitutes. The choice of host shall not
require code changes; only the deployment URL changes.
The project shall not operate any application server, database server,
job queue, or other persistent backend infrastructure. All ledger state
lives exclusively in (a) per-user OneDrive folders, accessed via Graph
(SC-ARC-PRV-3), and (b) per-device browser storage (SC-ARC-IDN-1,
SC-NFR-SEC-2).
from a public source repository. The repository shall include the build
configuration, dependency lockfiles, and a release procedure that pins
the exact commit deployed for each released version. Subresource Integrity
(SRI) hashes shall be used on all script and style tags whose contents
are loaded from the static host, so a tampered asset is detected and
refused by the browser.
the device shall generate a fresh random 256-bit AES key (the "data key")
using the browser's cryptographically secure random number generator
(crypto.getRandomValues). The data key shall never be transmitted to or
stored on the shared folder provider in any form. It shall exist only
in volatile memory on devices that have joined the ledger and in
IndexedDB on those same devices (see SC-ARC-ENC-5).
The MVP shall not offer an "unencrypted ledger" option. Encryption is
mandatory.
-
4.7. Segmented per-device logs SC-ARC-LOG-4 Each device's event log shall be split across one or more segment files in
the ledger folder. Segment files shall be named
"events/<device-uuid>/<segment-open-timestamp>.jsonl", where the timestamp
is the UTC instant the segment was opened, formatted as
"YYYYMMDDTHHMMSSsss" (millisecond precision) so that lexicographic file
ordering matches chronological order.
A device shall have at most one "open" segment per ledger at any time; all
other segments belonging to that device are "closed" and immutable. New
events are appended to the open segment. When the open segment's size on
disk would exceed the segment size threshold (SC-ARC-LOG-5) after writing
the next event, the device shall:
1. Close the current segment (no further writes to it, ever).
2. Open a new segment with the current UTC timestamp as its filename.
3. Append the new event to the new segment.
A device's full event log is reconstructed by reading every segment file
under that device's folder, sorted by filename. The concatenation is the
device's complete, append-only history.
AES-256-GCM using the ledger's data key. The on-disk file format for an
encrypted segment shall be:
- 12 bytes: a freshly-generated random IV (nonce)
- N bytes: the GCM ciphertext, where N equals the plaintext JSONL length
- 16 bytes: the GCM authentication tag
A fresh IV shall be generated for every encryption operation, including
every re-upload of the same logical segment after an append. Random 96-bit
IVs are acceptable because the collision probability over the lifetime of
any realistic ledger is negligible.
Decryption shall fail loudly on auth-tag mismatch. A segment that fails
to decrypt shall be reported as an error to the user, never silently
skipped, because it indicates either a wrong key, a corrupted file, or
tampering.
No plaintext segment file shall ever leave the device.
joining device can identify the folder as a SplitClone ledger before
holding the key. The metadata file shall include a "key fingerprint":
the lowercase hexadecimal representation of the first 16 bytes of
SHA-256(data_key).
When a user enters a join code, the app shall compute the fingerprint of
the imported key and compare it to the stored fingerprint. A mismatch
indicates a wrong join code and shall be reported as such; the key shall
not be persisted in IndexedDB until the fingerprint matches.
The truncated SHA-256 does not reveal the key (256-bit input space, only
128 bits of output displayed) and is short enough to log or display in
diagnostics if needed.
the key as a join code in two interchangeable forms:
(a) A QR code suitable for camera scanning device-to-device.
(b) A base64url-encoded string with a 4-character truncated-SHA-256
checksum appended (so typos are detected before the fingerprint
check in SC-ARC-ENC-3).
A device that has not yet joined a ledger shall accept the join code via
either form (scan-to-camera or paste). On successful import, the
fingerprint check (SC-ARC-ENC-3) shall pass before the key is persisted.
The join code shall never be transmitted by the app to any network
endpoint. It shall be communicated between users entirely out-of-band
(messaging app, in-person QR scan, etc.). The app shall include a clear
in-UI warning that the join code grants full access to the ledger's
contents and shall be shared only over a channel the user trusts.
IndexedDB under the app's origin, scoped to the ledger UUID. Where the
browser supports it, the key shall be imported as a non-extractable
CryptoKey via crypto.subtle.importKey so that crypto.subtle.exportKey on
the resulting object fails. The decryption and encryption operations
continue to work with non-extractable keys; only direct read-out of the
raw key material is prevented.
If the IndexedDB record holding the key is lost (browser data cleared,
private-browsing session, iOS Safari long-disuse eviction, fresh browser
install), the device shall be unable to read the ledger and the user
shall be required to re-enter the join code.
until acknowledged, the app shall prominently prompt the user to save
the join code as a recovery code in a safe place (their password
manager, a downloaded file, a printed copy). The prompt shall remain
visible until the user explicitly confirms they have saved it.
The app shall make it easy to:
- Reveal the current data key as a join code on demand from a joined
device.
- Download the join code as a small text file.
- Copy the join code to the clipboard.
The user shall be able to re-display the prompt at any later time from
the ledger settings.
segments under a new key, or revoking a member's access to an existing
ledger. A user who needs to revoke a former member shall be advised, via
in-UI guidance, to create a new ledger and re-enter the relevant expense
history manually (or by exporting and re-importing, when the relevant
import feature exists - see Roadmap).
app reads from or writes to non-volatile storage shared with users or
external tools. Specifically:
(a) The plaintext metadata file structure (SC-FR-LED-3).
(b) The event-log segment directory layout and filename pattern (SC-ARC-LOG-4).
(c) The encrypted segment envelope (IV / ciphertext / GCM tag, SC-ARC-ENC-2).
(d) The plaintext JSONL event schema inside a segment, including the payload schema of every event type (SC-ARC-LOG-2).
(e) The join-code encoding, in both QR and base64url-with-checksum forms (SC-ARC-ENC-4).
(f) The CSV export schema (SC-FR-EXR-4).
(g) The local backup file schema (SC-FR-BAK-1; docs/backup-format.md).
A single monotonically increasing integer schema version (declared in the
metadata file, SC-FR-LED-3) shall identify the current snapshot of the
format as a whole. A change to any of (a) - (f) requires incrementing the
schema version.
Item (g), the backup file, carries its OWN independent monotonically
increasing version integer ("splitcloneBackup"), declared inside the
backup file itself, plus an embedded copy of the (a)-(f) event-schema
version its contents are written in. It is versioned separately because a
backup is a self-describing one-shot artefact, not part of the
continuously-synced shared folder; a backup reader gates on those two
integers exactly as SC-ARC-FMT-2 gates the shared format (refuse a newer
file loudly; accept older). All other governance (SC-ARC-FMT-3,
changelog) applies to (g) unchanged.
The local browser-side caches (IndexedDB layout, derived-state cache
structure) are NOT part of the file format. They are private to a single
device and may evolve freely as long as the user-visible behaviour is
preserved.
-
4.25. Schema version handling SC-ARC-FMT-2 On reading a ledger, a device shall compare the schema version declared
in the metadata file (the "ledger version") against the highest schema
version it has been built to understand (the "device version"):
- If ledger version > device version: the device shall refuse to fold,
read, or write to the ledger. It shall display a clear error
explaining that the ledger was written by a newer version of the app
and shall suggest the user update the app. It shall not attempt
partial reads or "best effort" parsing.
- If ledger version == device version: normal operation.
- If ledger version < device version: the device shall read all events
and segments and shall perform any necessary upgrades to the older
schemas in memory only. New events shall continue to be written at
the ledger's current schema version (i.e. the older version), so
that other devices still on the older app continue to participate.
The ledger version shall never be incremented implicitly. Promoting a
ledger to a newer schema version shall be an explicit user action
("Upgrade ledger format") that re-confirms compatibility implications
and warns that older devices will stop being able to read the ledger
until they update.
Closed segments (SC-ARC-LOG-6) shall never be rewritten to convert their
schema version. Schema upgrades apply to new events going forward, not
to history.
-
4.26. Stability commitment post-v1.0 SC-ARC-FMT-3 Once SplitClone reaches its v1.0 release, the file format defined in
SC-ARC-FMT-1 shall be treated as stable. After v1.0:
- Any proposed change to the file format requires explicit, recorded
approval from the project owner before implementation begins. Format
changes shall not be introduced as part of unrelated work or as a
side effect of refactoring.
- The project shall maintain a written, public changelog that
enumerates every defined schema version, the changes between
consecutive versions, and the in-memory migration logic that an
upgraded device applies when reading the older version.
- Schema-version increments shall be infrequent and shall batch
related changes together where possible, rather than incrementing
repeatedly for small additions.
Before v1.0, the format may evolve more freely, but every change shall
still be a deliberate decision and shall update the changelog.
-
4.24. Scope of "file format" SC-ARC-FMT-1 The "file format" of a SplitClone ledger comprises every layout that the
app reads from or writes to non-volatile storage shared with users or
external tools. Specifically:
(a) The plaintext metadata file structure (SC-FR-LED-3).
(b) The event-log segment directory layout and filename pattern (SC-ARC-LOG-4).
(c) The encrypted segment envelope (IV / ciphertext / GCM tag, SC-ARC-ENC-2).
(d) The plaintext JSONL event schema inside a segment, including the payload schema of every event type (SC-ARC-LOG-2).
(e) The join-code encoding, in both QR and base64url-with-checksum forms (SC-ARC-ENC-4).
(f) The CSV export schema (SC-FR-EXR-4).
(g) The local backup file schema (SC-FR-BAK-1; docs/backup-format.md).
A single monotonically increasing integer schema version (declared in the
metadata file, SC-FR-LED-3) shall identify the current snapshot of the
format as a whole. A change to any of (a) - (f) requires incrementing the
schema version.
Item (g), the backup file, carries its OWN independent monotonically
increasing version integer ("splitcloneBackup"), declared inside the
backup file itself, plus an embedded copy of the (a)-(f) event-schema
version its contents are written in. It is versioned separately because a
backup is a self-describing one-shot artefact, not part of the
continuously-synced shared folder; a backup reader gates on those two
integers exactly as SC-ARC-FMT-2 gates the shared format (refuse a newer
file loudly; accept older). All other governance (SC-ARC-FMT-3,
changelog) applies to (g) unchanged.
The local browser-side caches (IndexedDB layout, derived-state cache
structure) are NOT part of the file format. They are private to a single
device and may evolve freely as long as the user-visible behaviour is
preserved.
in the metadata file (the "ledger version") against the highest schema
version it has been built to understand (the "device version"):
- If ledger version > device version: the device shall refuse to fold,
read, or write to the ledger. It shall display a clear error
explaining that the ledger was written by a newer version of the app
and shall suggest the user update the app. It shall not attempt
partial reads or "best effort" parsing.
- If ledger version == device version: normal operation.
- If ledger version < device version: the device shall read all events
and segments and shall perform any necessary upgrades to the older
schemas in memory only. New events shall continue to be written at
the ledger's current schema version (i.e. the older version), so
that other devices still on the older app continue to participate.
The ledger version shall never be incremented implicitly. Promoting a
ledger to a newer schema version shall be an explicit user action
("Upgrade ledger format") that re-confirms compatibility implications
and warns that older devices will stop being able to read the ledger
until they update.
Closed segments (SC-ARC-LOG-6) shall never be rewritten to convert their
schema version. Schema upgrades apply to new events going forward, not
to history.
-
4.24. Scope of "file format" SC-ARC-FMT-1 The "file format" of a SplitClone ledger comprises every layout that the
app reads from or writes to non-volatile storage shared with users or
external tools. Specifically:
(a) The plaintext metadata file structure (SC-FR-LED-3).
(b) The event-log segment directory layout and filename pattern (SC-ARC-LOG-4).
(c) The encrypted segment envelope (IV / ciphertext / GCM tag, SC-ARC-ENC-2).
(d) The plaintext JSONL event schema inside a segment, including the payload schema of every event type (SC-ARC-LOG-2).
(e) The join-code encoding, in both QR and base64url-with-checksum forms (SC-ARC-ENC-4).
(f) The CSV export schema (SC-FR-EXR-4).
(g) The local backup file schema (SC-FR-BAK-1; docs/backup-format.md).
A single monotonically increasing integer schema version (declared in the
metadata file, SC-FR-LED-3) shall identify the current snapshot of the
format as a whole. A change to any of (a) - (f) requires incrementing the
schema version.
Item (g), the backup file, carries its OWN independent monotonically
increasing version integer ("splitcloneBackup"), declared inside the
backup file itself, plus an embedded copy of the (a)-(f) event-schema
version its contents are written in. It is versioned separately because a
backup is a self-describing one-shot artefact, not part of the
continuously-synced shared folder; a backup reader gates on those two
integers exactly as SC-ARC-FMT-2 gates the shared format (refuse a newer
file loudly; accept older). All other governance (SC-ARC-FMT-3,
changelog) applies to (g) unchanged.
The local browser-side caches (IndexedDB layout, derived-state cache
structure) are NOT part of the file format. They are private to a single
device and may evolve freely as long as the user-visible behaviour is
preserved.
SC-ARC-FMT-1 shall be treated as stable. After v1.0:
- Any proposed change to the file format requires explicit, recorded
approval from the project owner before implementation begins. Format
changes shall not be introduced as part of unrelated work or as a
side effect of refactoring.
- The project shall maintain a written, public changelog that
enumerates every defined schema version, the changes between
consecutive versions, and the in-memory migration logic that an
upgraded device applies when reading the older version.
- Schema-version increments shall be infrequent and shall batch
related changes together where possible, rather than incrementing
repeatedly for small additions.
Before v1.0, the format may evolve more freely, but every change shall
still be a deliberate decision and shall update the changelog.
browsers, with no native iOS, Android, or desktop binary. Minimum
supported targets are:
- Mobile: iOS Safari 17+ (covers iPhone and iPad), Chrome on Android 14+
- Desktop: current and previous major versions of Chrome, Edge, Firefox, Safari
The app shall be installable to the home screen via Safari's "Add to Home
Screen" on iOS and via the standard PWA install prompt on Chromium
browsers, including a Web App Manifest declaring icon, name, theme
colour, and standalone display mode.
-
5.2. Responsive layout SC-NFR-PLT-2 The UI shall use a responsive layout that adapts to viewport widths from
~320 px (smallest iPhone) up to typical desktop browser widths. iPad and
desktop are not separate code paths; they are simply wider breakpoints of
the same layout. The phone-portrait viewport is the design baseline; other
breakpoints are progressive enhancements.
-
5.1. Browser platform (MVP) SC-NFR-PLT-1 The MVP shall be a Progressive Web App running in modern evergreen
browsers, with no native iOS, Android, or desktop binary. Minimum
supported targets are:
- Mobile: iOS Safari 17+ (covers iPhone and iPad), Chrome on Android 14+
- Desktop: current and previous major versions of Chrome, Edge, Firefox, Safari
The app shall be installable to the home screen via Safari's "Add to Home
Screen" on iOS and via the standard PWA install prompt on Chromium
browsers, including a Web App Manifest declaring icon, name, theme
colour, and standalone display mode.
~320 px (smallest iPhone) up to typical desktop browser widths. iPad and
desktop are not separate code paths; they are simply wider breakpoints of
the same layout. The phone-portrait viewport is the design baseline; other
breakpoints are progressive enhancements.
internet connection. Local changes shall be queued in IndexedDB and pushed
to OneDrive automatically once connectivity is restored. A registered
Service Worker shall cache the static app shell so that the app launches
when offline.
Known limitation: iOS Safari evicts PWA storage (IndexedDB and Service
Worker caches) after extended periods of non-use (currently around 7
weeks). Users who open the app at least every few weeks will not be
affected; users returning after a long absence may need to re-download
the app shell and re-authenticate. The app shall detect lost local state
gracefully (re-fetch from OneDrive, re-prompt for OAuth) rather than
data-loss.
render within 1 second on supported iPhone hardware. Network sync may
complete asynchronously after the UI is interactive.
under a per-ledger 256-bit AES-GCM data key (see SC-ARC-ENC-1 through
SC-ARC-ENC-7). The only plaintext file in a ledger folder shall be the
minimal metadata file specified in SC-FR-LED-3.
The shared folder provider's own access controls (folder sharing,
permissions) remain in force and are the first line of defence; the
at-rest encryption layer is additional protection against accidental
over-sharing of the folder and against compromise or insider access at
the provider level. The two layers protect against different threats and
neither replaces the other.
Members who legitimately hold the data key (every joined device) can
read the full ledger. Encryption is not intended to compartmentalise
access between members of the same ledger.
the device UUID shall be stored in browser-local storage scoped to the
app's origin: IndexedDB for refresh tokens and the device UUID, in-memory
or sessionStorage for short-lived access tokens. None of these values
shall ever be written to the shared OneDrive folder.
The MVP shall use the OAuth 2.0 Authorization Code flow with PKCE (no
client secret), as appropriate for a public single-page application.
Refresh tokens shall be requested with the smallest scope set that
satisfies the SharedFolderProvider operations (SC-ARC-PRV-2).
The MVP acknowledges that browser-local storage is materially weaker than
a hardware-backed secure enclave (e.g. iOS keychain):
- IndexedDB contents are readable by any JavaScript executing on the same origin (XSS risk).
- No biometric gating is available to the browser.
- Browser extensions with sufficient permissions may inspect storage.
Mitigations: a strict Content Security Policy restricting which sources
can execute scripts; no third-party analytics or trackers (SC-NFR-PRV-1);
keeping refresh-token lifetime as short as the OAuth provider permits;
clearing tokens on sign-out.
party. Diagnostic information shall remain on-device unless the user
explicitly exports it.
user-facing strings through an iOS localisation mechanism so that other
languages can be added later without code changes.