Commit graph

42 commits

Author SHA1 Message Date
Scott Reimers
2ce668aa58 People tab rewrite: recency sort, profile-post Discover, bio modal,
per-author feed filter, ignore primitive

The old People tab was built on network-layer presence (`is_online`,
`last_seen` from the mesh), which was lost when v0.6.1 anonymized the
network id from the posting id. Every named follow is authored under a
posting id that doesn't appear in the connection-layer tables; the
"Online" section listed nobody useful and Discover depended on the
same broken signal.

Replaced with signals derived from signed content:
- Following is sorted by most-recent-post timestamp (the real meaning
  of "activity" in a post-anonymization world).
- Discover lists named peers we've received signed profile posts from
  (via Phase 2d), filtered by follows / ignores / self.
- Click-a-name surfaces a bio modal with View Posts / Follow / Message
  / Ignore actions.
- Author-scoped feed filter (`View Posts` on any person) renders a
  "Showing posts from X" banner with a Clear button.
- Ignore is a new local-only primitive; ignored peers' posts and
  profiles are excluded everywhere and the ignored list is editable
  in Settings.

Core changes:

- New `ignored_peers(node_id, ignored_at)` table + storage helpers
  (`add_ignored_peer`, `remove_ignored_peer`, `list_ignored_peers`,
  `is_ignored_peer`). Schema created fresh; no migration since the
  table is purely additive and empty on prior installs.
- All 6 feed-query sites now also exclude `author IN ignored_peers`.
- New `Storage::last_activity_for_authors(&[NodeId])` — one batched
  query returning max post timestamp per author, excluding non-feed
  intents (Control / Profile / Announcement / GroupKeyDistribute).
- New `Storage::list_discoverable_profiles(&self_id)` — named profile
  rows where node_id is not self, not in follows, not in ignored, and
  `public_visible = 1`. Sorted by profile `updated_at` DESC.
- New `Storage::delete_setting(key)` — missing counterpart to
  set/get.
- Node wrappers: `last_activity_for_follows`, `ignore_peer`
  (also drops any follow + social route for the ignored peer),
  `unignore_peer`, `list_ignored_peers`, `list_discoverable_profiles`.
- `list_follows` Tauri command now sources `last_activity_ms` from
  the posts-driven batched query rather than the network peer record.

Tauri commands: `list_discover`, `ignore_peer`, `unignore_peer`,
`list_ignored_peers`.

Frontend:
- Following list: see-new-activity button pattern (staged data +
  explicit user click to rearrange, so the list doesn't reorder under
  a tap mid-scroll). Periodic people-tab polling stages + lights up
  the button; clicking it re-renders.
- Discover: rewrites the old peer-table-based list to a profile-post
  feed. Each card shows name + bio + profile-update age, plus Follow
  / Posts / Ignore actions.
- Bio modal: reuses the existing generic popover. Loads display name
  + bio via `resolve_display`, shows follow state, offers View Posts /
  Follow-or-Unfollow / Message / Ignore-or-Unignore.
- Author filter: banner renders at the top of the feed when active;
  clear button restores full feed. Filter state is a single `authorFilterNodeId`
  field consumed by `filterFeedPosts`.
- Settings → Ignored section lists ignored peers with unignore buttons.

124 / 124 core tests pass.
2026-04-23 12:15:51 -04:00
Scott Reimers
e74bd4e6c6 Profile-post backfill + prune disposable first-run persona on import
Two bugs the v0.6.2 Discover story was going to expose:

1) Many named personas don't have a profile post yet, so the planned
   profile-post-driven Discover listing would show them as headless.
   Existing personas predate the Phase 2d profile-post primitive, and
   imported personas arrive with a display_name but no matching post.

2) Fresh install always generates a blank disposable persona before
   the user can pick fresh-vs-import. If the user picks import, that
   blank persona lingers forever — visible in the Personas list, a
   potential default, confusing.

Fixes:

Profile-post backfill (node.rs):
- New `Node::backfill_profile_posts_for_named_personas`: scans posting
  identities, skips unnamed or already-covered ones, and emits a
  signed profile post at the persona's own `created_at` timestamp. Run
  once from `Node::open_with_bind` after migrations. Idempotent via
  the new `Storage::has_profile_post_by_author` check. Chronology is
  preserved (post timestamp matches persona creation), so a later
  genuine profile update the user authors always wins the `> old_ts`
  monotonicity check on receivers.
- `Node::create_posting_identity(name)`: if `name` is non-empty, emits
  the profile post inline so new named personas are Discover-able the
  moment they're created. Uses the new `publish_profile_post_as`
  helper, which signs with the persona's own secret (not the default
  posting secret) and propagates via `update_neighbor_manifests_as`.

First-run persona marker + targeted prune (node.rs + storage.rs + import):
- `Node::open_with_bind`'s auto-gen block now also writes
  `first_run_auto_persona_id = <hex>` into the settings kv. This marks
  the specific disposable persona from the fresh-install flow; later
  prune logic uses this id, not a "any empty persona" rule, so manual
  empty personas are never touched.
- `Node::try_prune_first_run_auto_persona`: deletes the marked id iff
  all four gates pass — still exists, no display_name, no authored
  posts, no authored reactions/comments, and it's no longer the
  current default. Any one failure → clear the marker and keep the
  persona. New storage helpers `has_any_post_by_author`,
  `has_any_engagement_by_author` back the check.
- `set_profile` clears the marker when a non-empty name lands on the
  marked persona (user claimed it).
- `storage.delete_setting(key)` — new one-line helper.
- `import_as_personas_cmd` in tauri-app calls the prune after import
  completes; the cmd's return message reports "(cleared blank starter
  persona)" when it fires.
- New `get_first_run_auto_persona_id` Tauri command so the frontend
  can filter the blank persona out of the Personas list while it
  still exists.
- Frontend `loadPersonas` filters the marker id out of
  `personasCache` before rendering.

Tests: 124 / 124 core tests pass.
2026-04-23 08:47:30 -04:00
Scott Reimers
481e1c8435 Network-wide announcements signed by the bootstrap anchor posting id
New primitive: `VisibilityIntent::Announcement`, a public post whose
author MUST be the hardcoded bootstrap anchor posting identity
(`DEFAULT_ANCHOR_POSTING_ID`) and whose content carries an ed25519
signature by that key. Forged announcements (any other author, or
bad signature) are rejected by `control::receive_post` before storage
— they never enter the DB and never propagate via neighbor-manifest
diffs. Only the real anchor can publish announcements, and it does so
sparingly as part of the release deploy flow.

Uses release announcements to drive an in-app upgrade banner:
- Anchor publishes a signed `{category:release, version, channel,
  download_url, ...}` post during every deploy.
- Clients receive it via the normal CDN; `apply_announcement_if_applicable`
  stores the latest-per-category/channel in the settings kv, keyed
  e.g. `announcement:release:stable`.
- Welcome screen checks storage on startup; if the stored release
  version > CARGO_PKG_VERSION on the user's selected channel, a banner
  appears with a Download button that opens the system browser.
- Settings gets "Updates" section with Stable / Beta radio + Check-now
  button + current status line.

Core:
- `DEFAULT_ANCHOR_POSTING_ID: NodeId` constant (32 bytes, the anchor's
  current posting id — `17af141956ae...`).
- New `VisibilityIntent::Announcement` variant; feed filters in all 6
  `get_feed*` / `list_posts*` query sites updated to also exclude the
  new intent AND the pre-existing `GroupKeyDistribute` intent.
- `types::AnnouncementContent` + `ReleaseAnnouncement` structs.
- `crypto::{sign,verify}_announcement` — length-prefixed field digest
  with a "has release" 1-byte flag.
- New `announcement` module with `verify_announcement_post`,
  `apply_announcement_if_applicable`, `latest_release`,
  `build_announcement_post`, and a `StoredAnnouncement` envelope saved
  to settings so the UI can render without a full post scan.
- `Node::publish_announcement` refuses to run unless the default posting
  id equals the anchor constant — accidental use on client installs
  fails loud.

Wire / receive:
- `control::receive_post` verifies announcement signatures upfront
  alongside Control and Profile. Same pattern; same guarantees.

CLI one-shots (no daemon):
- `itsgoin <data_dir> --print-identity` — prints network_id +
  default_posting_id, exits.
- `itsgoin <data_dir> --announce --ann-category release
  --ann-channel stable --ann-version X --ann-title ... --ann-body ...
  --ann-url https://itsgoin.com/download.html` — builds + stores +
  propagates the signed post, exits.

deploy.sh:
- Now runs the announce one-shot inside the anchor-restart window
  (after binary swap, before start). The DB is free during that gap,
  so the one-shot can write without conflicting with the running
  daemon. The restarted daemon loads all storage on boot and serves
  the new announcement to pulling peers.

Tauri IPC:
- `check_release_announcement(channel)` → Option<ReleaseAnnouncementDto>
  — returns None when up-to-date.
- `get_update_channel` / `set_update_channel(channel)` — persists in
  settings kv key `ui_update_channel`; defaults to stable.
- `open_url_external(url)` — desktop-only (xdg-open / open / cmd start);
  refuses non-http(s) URLs. Android needs the opener plugin — TODO.

Frontend:
- Upgrade banner on the welcome screen, populated by
  `loadUpgradeBanner()`. Hidden when no newer release is known.
- Settings → Updates section with Stable/Beta radio + Check-now button
  + current status line.

Tests: announcement signature roundtrip; non-anchor author rejection;
non-announcement intent is a no-op. 124 / 124 core tests pass.
2026-04-23 01:50:12 -04:00
Scott Reimers
de6aa06acf v0.6.2 release: version bump + changelog
Six phase commits landed for v0.6.2 (2b through 2g) plus three
pre-release fixes from the final audit pass:

- 2b: control-post flow (delete / visibility change) + retire BlobDeleteNotice
- 2c: remove audience primitive + retire PostPush / PostNotification /
  AudienceRequest / AudienceResponse
- 2d: profile posts signed by the posting identity
- 2e: rich comments with ref_post_id + signed preview
- 2f: groups as a distinct primitive alongside circles
- 2g: GroupKeyDistribute → encrypted post (last persona-signed
  direct push gone)
- audit fix: reject group-key distribution posts where the claimed
  admin doesn't match the post author
- audit fix: cap concurrent port-scan hole punches at one (the
  10 Mbps-on-VPN storm)
- audit fix: dedup concurrent outgoing connects to the same peer

Wire-breaking fork from v0.6.1. Retired message types 0x42
(PostNotification), 0x43 (PostPush), 0x44 (AudienceRequest), 0x45
(AudienceResponse), 0x95 (BlobDeleteNotice), 0xA0 (GroupKeyDistribute)
are not optional.

121/121 core tests pass.
2026-04-22 23:54:40 -04:00
Scott Reimers
f88618bb6f Phase 2g: GroupKeyDistribute \u2192 encrypted post
Removes the last persona-signed direct push on the wire. Group/circle
seeds no longer travel via the 0xA0 `GroupKeyDistribute` uni-stream from
admin to member. Instead the admin publishes an encrypted post containing
the seed + metadata; each member is a recipient; the post propagates via
the normal CDN. Members decrypt with their posting secret to recover the
seed.

Eliminates the wire-level coordination signal between an admin endpoint
and each member endpoint when a group is created, a member is added, or
a key is rotated.

Core pieces:
- New `VisibilityIntent::GroupKeyDistribute` variant.
- New `types::GroupKeyDistributionContent` — JSON payload inside the
  encrypted post: group_id, circle_name, epoch, group_public_key, admin,
  canonical_root_post_id, group_seed.
- New `group_key_distribution` module:
  - `build_distribution_post(admin, admin_secret, record, group_seed, members)`
    returns `(PostId, Post, PostVisibility::Encrypted)` — wraps the CEK
    per member using standard `crypto::encrypt_post`.
  - `try_apply_distribution_post(s, post, visibility, our_personas)`
    iterates every posting identity's secret trying to decrypt; on
    success stores `group_key` + `group_seed` and returns true.
  - `process_pending(s, our_personas)` scans stored
    GroupKeyDistribute-intent posts and applies any we can decrypt.

Node API:
- `add_to_circle`: builds a distribution post wrapping the current seed
  to just the new member, stores with intent=GroupKeyDistribute, and
  propagates via `update_neighbor_manifests_as` (no direct push).
- `create_group_key_inner`: at group creation, after wrapping keys for
  every non-self member, builds one distribution post addressed to all
  of them and propagates through the CDN.
- `rotate_group_key`: same pattern at epoch rotation.
- New `Node::process_group_key_distributions` — scans and applies.
  `sync_all` now calls it automatically so seeds take effect right after
  a pull cycle.

Removals (wire-breaking; v0.6.2 already forked):
- MessageType 0xA0 (`GroupKeyDistribute`), its payload struct, the
  handler in connection.rs, and `Network::push_group_key` all deleted.

ConnectionManager's `secret_seed` (network secret) is no longer used for
group-key unwrapping — that shifted to posting secrets in the apply
pass, matching the v0.6.1+ identity split where group keys are wrapped
to posting NodeIds.

Tests: new `member_decrypts_and_applies` covers a recipient decrypting +
storing the seed and a non-recipient failing to apply. Workspace
compiles clean; 118 / 118 core tests pass on a stable run (pre-existing
flaky `relay_cooldown` test with a 1ms timing window is unrelated).
2026-04-22 23:09:19 -04:00
Scott Reimers
eabdb7ba4f Phase 2c: remove audience + PostPush + PostNotification + AudienceRequest/Response
v0.6.2 wire fork: every persona-identifying direct push is gone. Public posts
propagate only through the CDN (pull + header-diff neighbor propagation).
Encrypted posts propagate only through pull with merged author-or-recipient
match. There is no remaining sender→recipient traffic correlation signal on
the wire for content.

Protocol (network-breaking):
- Retire MessageType 0x42 (PostNotification), 0x43 (PostPush),
  0x44 (AudienceRequest), 0x45 (AudienceResponse). Their payload structs are
  deleted along with the handlers and senders.
- SocialDisconnectNotice (0x71) / SocialAddressUpdate (0x70) sender
  functions targeting audience are deleted; the existing handlers stay
  (both already dead code on the send side).

Core removals:
- `push_to_audience`, `notify_post`, `push_delete`,
  `push_disconnect_to_audience`, `push_address_update_to_audience`,
  `send_audience_request`, `send_audience_response`, `send_to_audience` —
  all gone from network.rs.
- `handle_post_notification` removed from connection.rs.
- `request_audience`, `approve_audience`, `deny_audience`,
  `remove_audience`, `list_audience_members`, `list_audience` removed from
  Node.
- `audience_pushed` step removed from post creation.
- `AudienceDirection`, `AudienceStatus`, `AudienceRecord`,
  `AudienceApprovalMode` removed from types.
- Storage: `store_audience`, `list_audience`, `list_audience_members`,
  `remove_audience`, `row_to_audience_record`, `audience_crud` test, the
  `audience` CREATE TABLE, and the audience-dependent social route rebuild
  branch all removed. Upgraded DBs retain the orphan `audience` table;
  nothing touches it.

Follow-on cleanups:
- `SocialRelation::Audience` + `::Mutual` collapsed into just `Follow`.
  The Display/FromStr impl accepts legacy "audience"/"mutual" strings from
  pre-v0.6.2 DBs and maps them to Follow.
- Blob-eviction priority function drops the audience factor; relationship
  is now own-author vs followed vs other. Tests updated accordingly.
- `CommentPermission::AudienceOnly` → `FollowersOnly`. Check uses the
  author's public follows (`list_public_follows`) rather than a separate
  audience table. `ModerationMode::AudienceOnly` similarly renamed.
- Follow/unfollow routines simplified: no audience downgrade logic;
  unfollow removes the social route entirely.

UI:
- CLI: `audience*` commands removed.
- Tauri: `AudienceDto`, `list_audience`, `list_audience_outbound`,
  `request_audience`, `approve_audience`, `remove_audience` commands
  removed from invoke_handler. Frontend: audience panel and audience/mutual
  badges removed; compose permission dropdown shows "Followers" instead of
  "Audience"; `loadAudience` is a no-op stub that hides any leftover DOM.

Tests: 111 / 111 core tests pass.

Breaking change: v0.6.2 nodes won't interoperate with v0.6.1 for delete
propagation, visibility updates, direct post push, post notifications, or
audience requests. Upgrade both ends.
2026-04-22 22:20:02 -04:00
Scott Reimers
36b6a466d2 Phase 2b: control-post flow (delete/visibility) + remove BlobDeleteNotice
Replaces two persona-signed direct pushes with CDN-propagated control posts:
a single `VisibilityIntent::Control` post type whose content is a signed
`ControlOp` the receiver verifies and applies. Deletes and visibility updates
now flow through the same neighbor-manifest CDN path as regular content — no
direct recipient push needed for persona-signed ops.

Core pieces:
- `VisibilityIntent::Control` + `VisibilityIntent::Profile` variants.
- `ControlOp::DeletePost` / `ControlOp::UpdateVisibility` (JSON, ed25519-signed
  by the target post's author over op-specific byte strings).
- `crypto::{sign,verify}_control_{delete,visibility}` signing primitives.
- `control::build_delete_control_post` + `build_visibility_control_post`
  for authors to construct control posts.
- `control::receive_post` — unified incoming-post path used by all 6 receive
  sites. Verifies control signatures BEFORE storing, so bogus controls never
  enter storage and can't be re-propagated via neighbor-manifest diffs.
- `control::apply_control_post_if_applicable` — executes the op under the
  same storage guard as the insert.

Feed filter:
- Feeds (`get_feed`, `get_feed_page`, `list_posts_page`,
  `list_posts_reverse_chron`) now exclude `Control` and `Profile` posts so
  they propagate + tombstone without surfacing.
- Sync/export path (`list_posts_with_visibility`) keeps its own unfiltered
  query so control posts still propagate via CDN.

Wire protocol:
- `SyncPost` carries `intent: Option<VisibilityIntent>` so control posts
  arrive with their intent preserved.
- `BlobDeleteNotice` (0x95) removed — orphan blobs on remote holders evict
  naturally via LRU rather than via a persona-signed push. Code path,
  payload, sender, tests, and `delete_blob_with_cdn_notify` all gone.

Tests: control delete roundtrip (apply + tombstone) and wrong-author
rejection (not stored, not applied). 112/112 core tests pass.
2026-04-22 21:17:34 -04:00
Scott Reimers
3c5b80d017 Fix Android pick_file trait imports
AndroidFs trait must be in scope for show_open_file_dialog / read
method resolution — AndroidFsExt alone isn't enough. Matches the
existing share_file pattern that does use both traits.

Caught on the v0.6.1 Android build; previous cargo check only
validated the desktop target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:44:58 -04:00
Scott Reimers
12e0d4eccc v0.6.1 release: version bump + changelog
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:57:28 -04:00
Scott Reimers
7e1e1dd738 Platform: Reset wipe, empty name, Android browse + backup-off, import as personas
Reset All Data:
- Sentinel now written at the app-level data_dir instead of the
  active identity's subdir. On Android the subdir path was never
  checked at startup, so reset silently did nothing.
- On detection, wipe EVERYTHING under the app data_dir: identity.key,
  itsgoin.db + WAL + SHM, blobs, all identity subdirs. Next launch
  is truly fresh — new network key, new posting key, no prior data.

First-run name:
- Display name is optional. Blank submits as anonymous.
- First-run modal + profile overlay placeholder updated to say
  "Display name (optional)".

Android file picker:
- pick_file on Android now uses tauri-plugin-android-fs'
  show_open_file_dialog (Storage Access Framework OPEN_DOCUMENT).
  Read the picked URI's bytes, stage them in the app's private cache
  as a timestamped file, return the staged path so existing
  import_* code can read it as a regular filesystem path.
- Zip filter passes application/zip + application/octet-stream (some
  file providers report the latter for .zip).

Android auto-backup off:
- AndroidManifest: allowBackup="false", fullBackupContent="false",
  dataExtractionRules pointing at new data_extraction_rules.xml
- New data_extraction_rules.xml excludes all domains from both
  cloud-backup and device-transfer. Prior default (allowBackup=true)
  silently replicated identity.key to Google Drive for any user with
  cloud backup on — which effectively published the root secret to
  a third party without asking. Users who want off-device backup use
  Settings -> Export (explicit zip they control).

Import as personas:
- New import_as_personas function in core/import.rs + new
  import_as_personas_cmd Tauri IPC.
- Reads identity.key from the bundle and adds it to posting_identities
  as a persona. Also reads posting_identities.json (v0.6+ bundles)
  and adds each entry. Dedupes by node_id.
- Posts stay AS-AUTHORED — original post_id, original author,
  original signatures, original wrapped_key recipients. No
  re-encryption. Content encrypted to any of the imported keys
  becomes decryptable because we now hold the secrets.
- Blobs, follows, profiles copied across.
- If current device has <=1 posting identity (the fresh-install one)
  and the bundle brings more, auto-switch the default to the first
  imported persona. Covers first-run-then-import flow cleanly.

Import wizard UI:
- New default option: "Restore as personas" — posts keep original
  authors; source's keys become personas you can post as.
- Old "Merge with decryption key" retained as "Consolidate under
  current default persona (requires source key)" for the case where
  a user intentionally abandons a persona.
- "Public posts only" and "Add as separate identity" retained.

deploy.sh made executable (chmod +x tracked).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:40:21 -04:00
Scott Reimers
8e35add7bb v0.6.0: hard network fork; ship multi-persona + CDN restructure
Phases 1-5 of the Identity Architecture rollout are on master; Phase 6
(rotating DM IDs) is deferred. This bumps the protocol-breaking
release: v0.5 and v0.6 cannot interoperate.

Packaging:
- Cargo + tauri.conf: 0.5.3 -> 0.6.0
- deploy.sh: drop -beta filename suffix
- download.html: single v0.6.0 section with fork notice at the top;
  changelog entry summarising the five phases and the schema migration;
  Phase 6's rotating-DM-ID status noted as deferred

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 23:58:35 -04:00
Scott Reimers
eea868b4cc Phase 5 (0.6.4-beta) frontend: Personas UI + compose picker + feed pills
Settings > Personas:
- List all held posting identities with display_name + truncated nodeId
- Default badge; Set-default / Delete buttons per non-default persona
- "New Persona" modal prompts for a display name and creates via IPC

Compose box:
- A #persona-select dropdown appears when 2+ personas exist
- doPost attaches postingIdHex to create_post / create_post_with_files
  when a non-default persona is selected

Tauri:
- create_post and create_post_with_files take an optional
  posting_id_hex; when present they route through create_post_as,
  otherwise through the default create_post_with_visibility
- PostDto gains asPersona: name of the authoring posting identity if
  the author matches any of our held personas
- is_me now recognises ALL our posting identities, not just the
  network key (both post_to_dto and post_to_dto_batch)

Feed:
- Per-post "(you) as <PersonaName>" label on own posts authored by a
  non-default persona
- Persona filter pill row above the feed (hidden for single-persona
  users); pills toggle between All and each persona; matches when
  post.author or post.recipients contains the selected posting id
- Applied after loadFeed initial render and after appendFeedPage so
  filter survives infinite-scroll

App.js:
- personasCache + loadPersonas() loaded on startup so compose picker
  is populated before the Feed tab mounts
- loadPersonas() also called when Settings tab opens

Backend was unchanged; only the UI and IPC surface expanded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 23:09:06 -04:00
Scott Reimers
7bdb2eb736 Phase 5 (0.6.4-beta) backend: multi-persona creation + post-as
Users can now hold multiple posting identities on one device and
publish content under any of them. Each persona has its own ed25519
key; peers see them as distinct authors with no link back to the
device's network identity.

Node methods:
- list_posting_identities() -> Vec<PostingIdentity>
- create_posting_identity(display_name) — generates a fresh ed25519
  key, persists, auto-follows self
- delete_posting_identity(node_id) — refuses to delete the default
- set_default_posting_identity(node_id) — validates identity exists;
  Node's cached default_posting_id/secret picks up on next restart
- create_post_as(posting_id, content, intent, attachments) — routes
  through a shared create_post_inner that takes posting_id +
  posting_secret as parameters

Post creation pipeline:
- create_post_with_visibility now delegates to create_post_inner
  using default_posting_id/secret
- create_post_inner threads posting_id / posting_secret through
  every content-signing, encryption, manifest, blob-header, and
  CDN-manifest step — the persona is fully honored end to end
- update_neighbor_manifests now takes a posting_id param too, so
  posts from persona X only update neighbor manifests for X's own
  prior posts

Tauri IPC:
- list_posting_identities / create_posting_identity /
  delete_posting_identity / set_default_posting_identity
- create_post_as with posting_id_hex + the same visibility params
  as create_post

CLI:
- personas / create-persona <name> / delete-persona <id>
- post-as <posting_id> <text>

Smoke-tested two-persona scenario:
- A creates "Work" persona; posts from default and Work
- B follows both; pulls from A; gets all three posts
- Authors are AB84BA... (Work) and 7CD949... (default) — distinct
  on the wire

Frontend UX (Settings > Personas, compose picker, filter pills,
merged feed labels) is scoped as a separate commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 23:00:21 -04:00
Scott Reimers
a2cc98cb25 v0.5.3-beta: version bump
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 17:39:18 -04:00
Scott Reimers
ffb13d6791 Our Info: display peer-observed external address
Store the external address reported by peers via your_observed_addr in
initial exchange. Display it in Our Info panel with NAT classification.
Replaces reliance on iroh's pkarr/STUN for external address discovery
while keeping clear_address_lookup() (no dns.iroh.link publishing).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 17:37:33 -04:00
Scott Reimers
d159abead4 v0.5.2-beta: version bump, changelog
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 15:42:30 -04:00
Scott Reimers
4220674960 Duplicate identity: user override with Continue Anyway button
When anchor reports duplicate identity, sync pauses and a red banner
shows with a "Continue Anyway" button. Clicking it clears the flag and
starts all sync tasks. Handles false positives from network changes
(WiFi/cellular/VPN switches) without requiring complex staleness checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 15:25:17 -04:00
Scott Reimers
288b53ffb1 Feed pagination, duplicate identity detection, pkarr leak fix, Android SAF
Feed pagination:
- Cursor-based pagination: get_feed_page/get_all_posts_page (20 posts/page)
- Batched engagement queries (3 bulk SQL queries instead of 4 per post)
- IntersectionObserver for infinite scroll (sentinel at midpoint)
- Viewport-based media loading (blobs only load when post enters view)
- Pre-fetch next page immediately after current page renders

Duplicate identity detection:
- Anchor detects when a NodeId is already mesh-connected during initial
  exchange and sets duplicate_active flag in response
- Client skips sync tasks when duplicate detected
- Frontend shows red warning banner

Privacy:
- Fixed pkarr leak: clear_address_lookup() removes default dns.iroh.link
  publishing. Only mDNS (local network) discovery enabled.

Android:
- SAF integration via tauri-plugin-android-fs: exports open native "Save As"
  dialog so users can save to Downloads/Drive/etc.
- Download/export paths use app data dir on Android (writable)
- File picker gated behind desktop cfg (blocking_pick not on Android)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 15:35:23 -04:00
Scott Reimers
5e7eed9638 Fast startup: defer bootstrap to background, lazy feed load
- Node::open_with_bind no longer runs bootstrap (anchor connect, NAT
  probe, referrals). New run_bootstrap() method called from background
  task after UI is live.
- Background tasks (pull cycle, diff cycle, etc.) start after bootstrap
  completes, not during block_on.
- Feed no longer pre-loaded during welcome screen readiness check.
  Ready button enables immediately after get_node_info succeeds.
- Feed loads on tab switch, not during startup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 17:19:05 -04:00
Scott Reimers
19a95b7c45 Fix file picker for Android: gate blocking_pick behind desktop cfg
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:49:56 -04:00
Scott Reimers
c40e093d01 v0.5.1-beta: version bump, changelog, download page update
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 14:06:26 -04:00
Scott Reimers
cba30a1bb3 AppImage video fix, proper import posts, first-run chooser, file pickers
- bundleMediaFramework: true — bundles full GStreamer plugin set in AppImage.
  Fixes WebKit hang on video/audio (missing appsink/autoaudiosink plugins).
  MSDK/VA plugins removed post-build to avoid Ubuntu DMA assertion crash.
- Import creates complete posts: BlobHeader, VisibilityIntent, pinned blobs,
  self-follow. Imported posts now indistinguishable from locally created ones.
- First-run chooser: Start Fresh or Import Identity on fresh install only.
  Profile setup shown for existing identities without display name.
- File pickers: native Browse buttons on export (folder) and import (ZIP)
  via tauri-plugin-dialog.
- Export path: relative paths resolved against home dir.
- Lightbox close: only on overlay/image click, not inner form content.
- Growth loop skips self as N2 candidate.
- Node shutdown on identity switch (prevents zombie background tasks).
- media-src CSP includes asset protocol.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:49:52 -04:00
Scott Reimers
ec731fdb4b First-run chooser, node shutdown on switch, file picker, export path fix
- First-run: show Start Fresh / Import chooser only when single auto-created
  identity with no profile (not on every boot without a display name).
- Identity switch: shut down old node's endpoint before starting new one.
  Fixes lockup after multiple switches (zombie background tasks).
- File picker: native Browse buttons on export (folder) and import (ZIP file)
  via tauri-plugin-dialog.
- Export path: resolve relative paths against home dir (was using process cwd).
- Lightbox close: only close on overlay/image click, not inner form content.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 01:58:02 -04:00
Scott Reimers
be253e8001 Our Info panel, hole punch race fix, NAT profiles in relay introduction
- Network Diagnostics: "Our Info" button shows addresses with NAT status,
  device role, UPnP, HTTP capability. Addresses stacked for mobile.
- Hole punch race: re-check for existing connection before and after relay
  introduction to avoid wasting minutes on redundant punch attempts.
- Relay introduction now carries requester/target NAT mapping+filtering
  so hole punch strategy uses fresh profiles instead of stale stored ones.
  Critical for phones that switch between WiFi/cellular/VPN.
- STUN fix: filter DNS results to IPv4 (was resolving to IPv6 first on
  dual-stack, causing silent send failure and "NAT unknown").
- Welcome screen: Ready button with loading bar for instant feed access.
- LAN addresses show just "LAN" (no misleading punchability label).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:57:41 -04:00
Scott Reimers
97dc83f9f1 v0.5.0-beta: merge-with-key import, prior_author provenance, beta versioning
Merge-with-key: decrypt exported posts using original identity seed, re-create
under current identity with prior_author in BlobHeader for provenance tracking.
Download page restructured with stable (v0.4.4) + beta (v0.5.0-beta) sections.
Version bumped across all crates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:47:24 -04:00
Scott Reimers
8ef32e6df6 Export/Import: ZIP export with scope selection, import with public post merge
Export (export.rs): ZIP archive with auto-chunking at 4GB. Four scopes:
identity only, posts only, posts+identity, everything (posts+key+follows+
profiles+settings). Includes blobs. Manifest JSON tracks metadata.

Import (import.rs): Read ZIP summary without importing (preview).
Import public posts into current identity with new PostIds + original
timestamps. Import as new identity (creates identity subdir from key).
Uses spawn_blocking for ZIP I/O to avoid Send issues with ZipArchive.

Tauri IPC: export_data, import_summary, import_public_posts,
import_as_new_identity commands. IdentityManager.base_dir() getter.

Frontend: Export wizard lightbox with scope radio buttons + output dir.
Import wizard with ZIP path, preview summary, action selection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 20:56:03 -04:00
Scott Reimers
18a40756d8 Identity IPC commands + frontend identity management UI
New Tauri commands: list_identities, create_identity, switch_identity,
delete_identity, import_identity_key, get_active_identity.

Settings UI: Identities section with list (active indicator, switch/delete
buttons), Create New Identity lightbox, Import Identity Key lightbox.
Export/Import buttons as placeholders for Phase 2/3.

Identity switch does hot-swap: tears down old Node, starts new one with
all background tasks, swaps AppNode RwLock. Frontend reloads after switch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:08:21 -04:00
Scott Reimers
75ce965b63 Multi-identity infrastructure: IdentityManager + Tauri AppState refactor
New identity.rs: IdentityManager manages per-identity data directories
under identities/{node_id_hex}/. Supports create, list, switch, delete,
import from key. Legacy flat layout auto-migrates on first launch.

Tauri AppState refactored from Arc<Node> to AppNode (Arc<RwLock<Arc<Node>>>)
+ AppIdentity (Arc<Mutex<IdentityManager>>). All 76 IPC commands updated
to get_node(&state).await pattern. Enables hot-swap identity switching.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:51:24 -04:00
Scott Reimers
926e0c1509 v0.4.4: UI overhaul — sticky header, mobile nav, profiles/redundancy lightboxes
Sticky header with tabs as one block on desktop. Fixed header + bottom nav on
mobile. Full-width dark header (#0a0a1a) edge-to-edge with 15px fade gradient.
Tab icons on desktop (inline) and mobile (stacked). Safe area inset support for
phone notches. Lightboxes close on tab switch.

Profiles lightbox (name, bio, visibility, circle profiles) and redundancy
lightbox moved from settings to My Posts. Sync All and Stored Anchors moved
into Network Diagnostics popover. Network indicator click opens diagnostics.
Settings streamlined — removed profile editor, diagnostics button, sync,
redundancy, anchor management.

Keepalive fix: tokio::time::sleep in select! never fired; switched to interval.
Auto-reconnect on unexpected disconnect with 3s delay. notify_growth on
disconnect. Tab badge fix preserving icon spans. Feed re-render skip during
media playback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:56:27 -04:00
Scott Reimers
43adbbdf7d v0.4.3: Lock contention overhaul, StoragePool, mobile bottom nav, text scaling
Eliminate all conn_mgr lock holds during network I/O across 14 actor commands
and bi-stream handlers. PostFetch, TcpPunch, PullFromPeer, FetchEngagement,
ResolveAddress, AnchorProbe use brief locks for data gathering only. WormLookup,
ContentSearch, WormQuery use connection snapshots for lock-free cascade fan-out.
RelayIntroduce extracts forwarding data under brief lock, does I/O outside.
BlobRequest, PostFetchRequest, ManifestRefresh use Arc clones instead of conn_mgr
lock. ConnectionActor hoists shared Arcs (storage, blob_store, endpoint) for
lock-free access. ResolveAddress adds 5s per-query timeout (was unbounded).

Initial exchange failure now aborts mesh upgrade (was silently continuing with
broken connection). connect_to_peer/connect_to_anchor use consistent 15s timeout.
Rebalance connects outside the lock via pending_connects pattern.

StoragePool: 8 concurrent SQLite connections in WAL mode replace single
Mutex<Storage>. Reads run fully parallel; writes serialize at SQLite level only.
PRAGMA busy_timeout=5000 for graceful write contention.

Mobile bottom nav bar (<=768px) with icon tabs. Text sizes: XS/S/M/L/XL
(75%/100%/125%/150%/200%), default M. localStorage persistence for instant
restore. Toast repositioned above mobile nav.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 21:35:38 -04:00
Scott Reimers
6004cae8a8 v0.4.2: Welcome screen, status ticker, notifications, text scaling, networking fixes
Welcome screen with staggered counters while backend bootstraps. Header status
ticker for new posts/messages/reactions/comments/connection changes. Notification
fallback chain (Tauri plugin → Web API → notify-rust). Responsive text scaling
(Small/Normal/Large, persisted). Diagnostics moved to popover with on-demand
connections. Share details lightbox with QR code. Connect string prefers external
address. Stale N1 fix (disconnected routes excluded). Replication handler actively
fetches posts+blobs from requester. Hole punch registers remote address for relay.
Replication semaphore (3 concurrent). Peer labels show truncated node ID.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 14:15:49 -04:00
Scott Reimers
bb6f2b64b0 v0.4.1: Security hardening, lock contention fixes, data cleanup
Security:
- Reaction signatures: ed25519 sign/verify (sign_reaction, verify_reaction_signature)
  Backward-compatible — unsigned reactions from old nodes still accepted
- Comment signature verification: verify_comment_signature now called on receipt
- Reaction removal authorization: only reactor or post author can remove
- BlobHeader author verification: lookup actual author from storage, don't trust payload

Lock contention (4 fixes):
- ManifestPush discovery: cm lock released before PostFetch I/O
- Pull request handler: load under lock, filter without lock, brief re-lock for is_deleted
- Pull sender: split into two brief locks (store posts, then batch upstream+sync)
- Engagement checker: batch all chunk results, single lock for writes

Data cleanup:
- Post deletion cleans post_downstream, post_upstream, seen_engagement tables
- Added TODO-hardening.md documenting remaining DOS/security/lock/data issues

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:30:38 -04:00
Scott Reimers
bbaacf9b6c v0.4.0: Protocol v4 — header-driven sync, tiered engagement, multi-upstream
Protocol v4 sync overhaul:
- Slim PullSyncRequest: per-author timestamps (since_ms) replace full post ID lists
  Request size O(follows) instead of O(posts). Backward-compatible via serde default.
- Tiered pull frequency: 60s ticks, only syncs stale authors (4hr default)
  Full pull only on first tick (bootstrap). Most ticks skip — no stale authors.
- Tiered engagement checks: frequency scales with content age
  5min (<72h), 1hr (3-14d), 4hr (14-30d), 24hr (>30d)
  Single SQL query filters posts due for check.
- Header-driven post discovery: ManifestPush triggers PostFetch for missing
  followed-author posts (capped 10 per manifest). CDN tree = notification system.
- Multi-upstream (3 max): composite PK, priority ordering, engagement diffs
  sent to all upstreams, promote/remove on failure.

DB schema:
- follows.last_sync_ms — Self Last Encounter per author
- posts.last_engagement_ms — last reaction/comment timestamp
- posts.last_check_ms — last engagement check timestamp
- post_upstream: single-row → 3-row with priority column

Lock contention fixes:
- get_blob_for_post: 3 locks → 1
- prefetch_blobs_from_peer: lock-free blob checks
- fetch_engagement_from_peer: explicit lock release before I/O
- serve_post: 4 locks → 2 (eliminated redundant queries)
- run_replication_check: 2 locks → 1
- Badge cycle: N+2 IPC calls → 1 (get_badge_counts)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:13:45 -04:00
Scott Reimers
89d6a853f5 Fix storage lock contention: reduce lock holds across 6 hot paths
- get_blob_for_post: 3 sequential locks → 1 combined acquisition
- prefetch_blobs_from_peer: lock only for DB reads, blob checks outside lock
- fetch_engagement_from_peer: explicit lock release before next network I/O
- serve_post: 4 locks (2 redundant) → 2
- run_replication_check: 2 locks → 1 combined
- Badge cycle: N+2 IPC calls → 1 (new get_badge_counts command)
- Follow timeout: 15s cap on auto-sync-on-follow to prevent UI hang
- Notification clearing: clear system notifications on conversation read

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:02:30 -04:00
Scott Reimers
3cc39590a7 Design doc audit: update badges, fix outdated descriptions, add CDN/replication docs
Badge updates:
- BlobHeader: Planned → Complete (has receipt/comment slots, reactions, policy)
- LAN Discovery: Planned → Complete (iroh mDNS integration)
- UPnP TCP: Planned → Complete (both UDP+TCP renewal cycles)
- HTTP Post Delivery: added Complete badge

Description fixes:
- Share links: removed hostlist encoding, added tiered serving (redirect → punch → proxy)
- Eviction formula: added share_boost factor (+100 for 3+ downstream)
- Message types table: added ReplicationRequest/Response (0xE1/0xE2), count 41 → 49
- Engagement: added tombstone propagation description

New sections:
- Device roles & bandwidth budgets (Intermittent/Available/Persistent)
- Active CDN replication (10-min cycle, target prioritization, graceful degradation)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 00:20:47 -04:00
Scott Reimers
24b78a8d41 v0.3.6: Network indicator, tab badges, message read tracking, UI cleanup
UI:
- Network indicator dot (black/red/yellow/green) + capability labels (Public, Server)
- Tab badges: Feed (new posts), My Posts (new engagement), People (online), Messages (unread)
- Stats bar removed — contextual counts in tab labels
- Message thread popup variable scoping fix

Message read tracking:
- mark_conversation_read on popover open, close, and message send
- Prevents re-notification of already-seen messages after app restart

Network:
- Added has_public_v6(), has_upnp() getters on Network
- NetworkSummaryDto includes hasPublicV6, hasPublicV4, hasUpnp

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 23:09:57 -04:00
Scott Reimers
a7e632de88 v0.3.6: Active CDN replication, device roles, budgets, tombstones, engagement fix, DOS hardening
Active CDN replication:
- All devices proactively replicate recent posts (<72h, <2 replicas) to peers
- Target priority: desktops (300) > anchors (200) > phones (100) + cache_pressure
- ReplicationRequest/Response (0xE1/0xE2) wire messages
- 10-min cycle, 2-min initial delay, cap 20 posts per request
- Graceful with small networks (1 peer = 1 replica, 0 peers = silent skip)

Device roles & budgets:
- Intermittent (phone), Available (desktop), Persistent (anchor)
- Advertised in InitialExchange, stored per-peer
- Replication budget: phones 100MB/hr, desktops/anchors 200MB/hr
- Delivery budget: phones 1GB/hr, desktops 2GB/hr, anchors 1GB/hr
- Hourly auto-reset, enforcement on blob serving

Cache management:
- 1GB default cache limit, configurable in settings UI
- Eviction cycle activated (was implemented but never started)
- Share-link priority boost (+100 for 3+ downstream)
- Cache pressure score (0-255) for replication targeting

Engagement distribution fix:
- BlobHeader JSON rebuilt after BlobHeaderDiff ops
- Previously reactions/comments stored in tables but header stayed stale

Tombstone system:
- deleted_at column on reactions and comments
- Tombstones propagate through pull sync (additive merge respects timestamps)
- UI queries filter WHERE deleted_at IS NULL

Persistent notifications:
- seen_engagement and seen_messages tables replace in-memory Sets
- Only notify on genuinely unseen content, survives restarts

DOS hardening:
- BlobHeaderDiff fan-out: single batched task, max 10 concurrent via JoinSet
- Blob prefetch: cap 20 per cycle, newest first
- PostDownstreamRegister: cap 50 per sync
- Delivery budget enforcement on BlobRequest handler
- Pull preference: non-anchors first to preserve anchor delivery budget

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 21:00:28 -04:00
Scott Reimers
b7f2d369fa v0.3.5: Encrypted receipt & comment slots, message delivery indicators
Encrypted slots in BlobHeader:
- Private posts get noise-prefilled receipt slots (64B, 1 per participant)
  and comment slots (256B, ceil(participants/3), expandable)
- Slot key derived from post CEK via BLAKE3 — only participants can read
- CDN relays propagate opaque encrypted bytes without decryption
- 3 new BlobHeaderDiffOps: WriteReceiptSlot, WriteCommentSlot, AddCommentSlots

Receipt system:
- States: empty(0), delivered(1), seen(2), reacted(3)
- Slot index = position in sorted participant NodeId list
- Author can pre-feed emoji reaction at creation time
- 6 new crypto tests for slot encrypt/decrypt/derivation

Node methods:
- write_receipt_slot, write_comment_slot with upstream+downstream propagation
- read_receipt_slots, read_comment_slots with CEK-based decryption
- get_post_cek_and_participants helper for both Encrypted and GroupEncrypted

IPC: write_message_receipt, write_message_comment, get_message_receipts,
     get_message_comments

Frontend:
- DM chat bubbles show delivery indicators (check → double → blue → emoji)
- Opening conversation auto-marks incoming messages as seen
- React button on messages with emoji prompt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 14:15:33 -04:00
Scott Reimers
a41b11c0b8 v0.3.5: Private blob encryption, blob prefetch, intent-based filtering, crypto refactoring
Private blob encryption:
- Encrypted posts (Friends/Circle/Direct) now encrypt attachment blobs with same CEK
- Public blobs unchanged, CID computed on ciphertext for private
- decrypt_blob_for_post/get_blob_for_post for transparent decryption on retrieval

Blob prefetch:
- Pull cycle and sync_with eagerly fetch missing blobs after post sync
- prefetch_blobs_from_peer scans for missing attachments, fetches via fallback chain
- Runs outside conn_mgr lock at Node level

Crypto refactoring:
- Extracted: encrypt/decrypt_bytes_with_cek, wrap/unwrap_cek_for_recipients
- unwrap_cek_for_recipient, unwrap_group_cek, random_cek
- encrypt_post_with_cek, encrypt_post_for_group_with_cek variants
- All existing functions refactored to delegate, 19 crypto tests pass

Intent-based filtering:
- intent_kind field on PostDto ("public"/"friends"/"circle"/"direct"/"unknown")
- Feed/MyPosts filter on intentKind !== 'direct' instead of visibility
- Messages filter with backward-compatible fallback for pre-intent posts
- get_post_intent storage method

IPC updates:
- resolve_blob_data helper using get_blob_for_post with network fallback
- sanitize_download_filename prevents path traversal
- get_blob_path accepts optional post_id_hex

Website:
- Mobile hamburger nav on all pages
- Mesh/Non-mesh N1 labels in network diagnostics

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:44:07 -04:00
Scott Reimers
0abc244ee9 v0.3.4: Comment edit/delete, native notifications, forward-compatible protocol, UI fixes
Comment edit & delete:
- EditComment/DeleteComment BlobHeaderDiffOps with upstream+downstream propagation
- Trust-based: comment author can edit/delete, post author can delete
- Storage: edit_comment(), delete_comment() methods
- Frontend: inline edit (Enter/Escape), delete with confirm

Native notifications:
- tauri-plugin-notification for system notifications on all platforms
- Triggers for messages, new posts, reactions, and comments
- notif_reacts setting added, button-group toggles replace dropdowns
- _notifReady flag prevents startup spam

Protocol hardening:
- BlobHeaderDiffOp::Unknown variant with #[serde(other)] for forward compatibility
- Old nodes silently skip unknown ops instead of crashing

UI fixes:
- Self removed from Following list
- Offline follows in lightbox popup (auto-show if no one online)
- Sent DMs filtered from My Posts
- Comment threading scoped to closest .post (fixes duplicate ID issue)
- Select dropdown text legible in WebKitGTK (black on white options)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:47:53 -04:00
Scott Reimers
8fad30cf95 v0.3.3: Rate limiting, IPv6 fix, schema versioning, video preload, engagement propagation
Security & stability:
- Incoming auth-fail rate limiting per source IP (3 attempts, then exponential backoff)
- Schema versioning via PRAGMA user_version with migration framework

Networking:
- IPv6 http_addr fix: advertise actual public IPv6 instead of 0.0.0.0
- N2/N3 TTL reduced from 7 days to 5 hours
- Full N1/N2 state re-broadcast every 4 hours
- Bootstrap isolation recovery: 24h check with sticky N1 advertising
- Bidirectional engagement propagation (upstream + downstream)
- Auto downstream registration on pull sync and push notification
- post_upstream table for CDN tree traversal

Media & UI:
- Video preload="auto" for share links and in-app blob URLs
- Following: Online/Offline split with last-seen timestamps
- DMs filtered from My Posts tab
- Image lightbox, audio player, file attachments with download prompt
- Share link unroutable address filtering

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 18:37:24 -04:00
Scott Reimers
800388cda4 ItsGoin v0.3.2 — Decentralized social media network
No central server, user-owned data, reverse-chronological feed.
Rust core + Tauri desktop + Android app + plain HTML/CSS/JS frontend.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 20:23:09 -04:00