Concord V2-#71
Open
alexgleason wants to merge 428 commits into
Open
Conversation
In-panel square cropper replaces the square-or-reject rejection for non-square uploads. Static formats (PNG/JPEG/static-WebP) decode + re-encode in place; animated formats (GIF, animated WebP) decode per-frame, crop each, and re-encode preserving per-frame durations. Animated WebP cumulative timestamps are reconstructed from the decoder's per-frame deltas. UX: drag-pan inside the box, 1:1-locked corner resize, marquee draw from any image-rect pointerdown, live preview chip that animates the post-crop result. Pointer capture on the stage unifies all three gestures into one event channel. Esc cancels, Enter confirms. Emoji cells gain a long-press / right-click context menu (Rename / Delete) and the hover-× delete badge is hidden on mobile — the gap that previously left no path to delete emojis on touch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Kind 10030 is now published with an empty public `tags` array — the entire subscription set lives inside the event's NIP-44-self-encrypted `content` as a JSON array of `["a", "30030:pk:d"]` tuples. Only the user's own signer can decrypt and reconstruct the pack list, so peers scraping relays see opaque ciphertext instead of a public list of which emoji packs the user follows. The read path mirrors the write path: kind 10030 with empty content, or content that fails to decrypt, is treated as no subscriptions rather than failing the whole refresh. No public-tag fallback — Vector treats the emoji list as fully private by design. Encrypted-items schema matches what NIP-51-aware clients (amethyst, snort) use for mute lists, keeping cross-client compatibility for any client that adopts emoji-pack support. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
square thumbs - Picker hover (native CSS + canvas): background highlight alpha dropped 25%, scale upscale raised 25% (1.10 → 1.125). More reactive feel, lighter wash. - Stranded tooltip on keyboard close: a MutationObserver on the picker's `class` attribute dismisses the shared emoji tooltip whenever `visible` comes off, so Enter/Esc-triggered closes don't leave a tooltip floating with no mouseleave to dismiss it. - Favorites section hidden until implemented — both the sidebar category button and the main-area section. Explicit `[hidden]` rules override `display: flex` on the button and a JS path that was forcing `display: block` on every `.emoji-section` after search clear. - `object-fit: fill` → `contain` on the in-chat pack preview thumbs and the Pack Details modal thumbs. The preview-grid cells are wider than tall (`repeat(6, 1fr)` × 28px), so `fill` was squashing square emojis horizontally. `contain` preserves the natural aspect. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- message_new / mls_message_new handlers double-pushed into chat.messages
when it aliased eventCache's entry.events after openChat. The duplicate
sat at the tail; on reopen, findIndex(lastReadOnOpen) returned the first
copy and the loop then saw the second copy as "another unread" — the
divider anchored above a message the user had already read.
- Expose eventCache.getEventsRef() so the message handlers can detect the
shared-array case and skip the redundant insertion.
- Tighten the read-state policy across all paths:
- Open chat → mark synchronously (OS badge clears immediately), with
last_read snapshotted first so the divider can still anchor above the
first missed message.
- Receive while pinned → mark + clear divider.
- Receive while scrolled up → don't mark, divider stays.
- Scroll back to pin → mark (badge clears), divider stays.
- Close while pinned → mark.
- Close while scrolled up → don't mark; honestly preserves unread state.
- Remove the focus-gated markAsRead inside updateChat — the IPC could
hang/fail and leave last_read stuck. Direct site-level marks above
cover every case more reliably.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add New Themes: XMR & Neon
Back stack - src/js/back-stack.js: lightweight pushBack/popBack/runBack/clearBack stack, deduped by id. window.__vectorOnAndroidBack is the JS entry point Kotlin calls via WebView.evaluateJavascript. - MainActivity.kt: onBackPressed forwards into JS; the async callback hits moveTaskToBack(true) when the stack reports unhandled, so the activity hides instead of tearing down (preserves relay/MLS state). - Screens wired: chat, new-chat, profile (chat-aware close), settings, invites, create-group, group-overview, invite-member modal. openChatlist clears the stack since it's the root. - Transient surfaces wired: popupConfirm (back = cancel/accept by fNotice), emoji picker + attachment panel via MutationObserver on .visible, Mini Apps sub-view, Marketplace + App Details + Permission Prompt, and the Mini App launch dialog. Read-state policy alongside - Tabbed-out / backgrounded receives no longer auto-mark while open and pinned. Refocus / foreground catches up when pinned via onFocusChanged + visibilitychange. UX polish - Mini profile popup now drops a backdrop on desktop too — focused-modal feel without losing the click anchor. - Reaction custom emojis can't be dragged or selected; pointer-events passes the click through to the reaction chip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Emoji Pack Opacity Fix & Scroll Bar Fix
Emoji Panel UX/UI Polish
Wallpapers: pick an image, AES-256-GCM encrypt, upload to Blossom, and publish a NIP-17 gift-wrapped kind-30078 (d=vector-wallpaper) rumor to the peer. Receiver downloads, decrypts, caches, and applies it. Latest-write- wins by rumor created_at; blur + brightness sliders persisted in tags; animated images get first-frame extraction; auto-brightness from image luma. Replacing a wallpaper DELETEs the previous Blossom blob, but only when we were the original uploader (tracked via wallpaper_url + wallpaper_uploader, migration 31). Read-badge fix: a backend ACTIVE_CHAT marker (set by the frontend when the chat is open + pinned + focused) lets the inbound handler advance last_read on arrival, so the dock badge never bumps for messages the user is watching. Send path: normalise relay URLs before pool lookup (trailing slash / case drift was dropping visually-identical inbox relays), and split send-failure handling so "relays rejected" and "couldn't attempt" report distinct, actionable errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Custom emoji: hold a transparent pixel (not a src-less img) during the loading phase so Android WebView stops painting its broken-image glyph behind the shimmer. When an emoji's bytes can't be fetched (uncacheable URL or a 404 on a deleted blob), fall back per context — inline messages show the literal :shortcode:, reactions show a twemoji'd question mark. Chat header overflow menu: derive the button's visibility from the same builder that populates it, so chats with no options (groups, which have no per-chat settings yet) hide the button instead of opening an empty menu. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixing Avatar Upload + Truncated Status on Chat Page
The synced Community List is a single NIP-44 event (65 KB plaintext) and
overflowed once an account held ~23 communities — the encrypted republish then
failed ("message too long") and cross-device sync stalled.
Slim each entry: drop `current` when it equals `seed` (the no-rekey case),
strip the icon from list blobs (a rehydrating device folds it from the
community metadata; the icon still rides the real invite bundle), and skip
serializing None/empty/zero fields. Real 21-community account: ~2,128 ->
~1,181 bytes/entry (-44%), raising the ceiling from ~23 to ~55. from_json
re-expands `current` so the in-memory form is uniform and the roundtrip is
identical; the icon strip also keeps canonical() (the cross-device tiebreak)
icon-invariant.
Cap memberships at 50 (MAX_COMMUNITIES): create_community and the invite-accept
paths reject a NEW membership past the limit (re-accepts are exempt), with a
clear message shown through the existing join/create error popups.
Fully backward-compatible: every change rides existing Option/default serde
semantics, no wire or schema break.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ear "Playing" on close - A broken .xdc no longer fails silently. The open error (e.g. "Invalid Mini App package: Missing index.html") is shown as a toast instead of a dead "Click to Play"; the throw happens before any optimistic status, so the card stays clickable. - A solo app (no realtime channel) no longer shows "1 online". The peer badge used the real session-peer count (which already includes self once a channel is joined) instead of fabricating self+1 from "window open". - A solo app's "Playing" now clears on close. The desktop WindowEvent::Destroyed handler and Android miniapp_close emit is_active:false for the app's topic even when no channel was joined (the clear was previously gated on a channel a solo app never creates), so the card resets live without a chat re-render. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…c loader) A .xdc whose entry is "Index.html" (or any off-case file) failed to open even though the bytes were fine. Resolve archive entries case-insensitively at every zip read: validation (index.html, manifest.toml), the runtime serve (get_file, the single chokepoint for every asset request), and the info-card manifest/icon. A mis-cased app now just runs. Exact match always wins first, so correctly-cased apps are unaffected; genuinely-broken packages still get the descriptive open-error toast. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…another device) A backgrounded message notification stayed floating after the chat was read by opening the app and navigating to it (instead of tapping the notification), or after answering on another device. There was no per-chat cancel: notifications only cleared on tap/swipe/Mark-Read, and onResume wiped the id map without ever cancelling the OS notifications. - VectorNotificationService.cancelNotification(context, chatId): dismiss the per-chat notification (no-op if none is showing) + drop its history. - clearAllMessageHistory (onResume) now KEEPS chatNotificationIds so a later per-chat cancel can still find a notification still showing after resume; opening the app no longer wipes the ability to dismiss them on read. - cancel_notification_jni + cancel_chat_notification: JNI bridge + platform helper (Android cancels; desktop no-op). - Cancel at every read chokepoint: mark_as_read / mark_as_read_headless (in-app open, window refocus, notification Mark-Read), and on_dm_received / on_file_received for own cross-device messages (mark read + cancel, in both the foreground handler and background-sync commit path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…at edge case) A message for the chat the user already had open, arriving while softly backgrounded, was auto-marked read but still posted a notification (the activity wasn't foreground). Returning to the app landed straight back in that chat, which never re-opens, so no read path re-fired a cancel and the notification lingered. Two parts: - nativeOnResume now cancels the active chat's notification on resume (only the active chat; others stay until read). It flips foreground=true first, so a post still in flight at the moment of resume is suppressed rather than re-shown. - Notification IDs are now deterministic (chatId.hashCode()) instead of a mutable counter map. The map made cancel unreliable: a message-arrival race between the read-path cancel (which removed the map entry) and the re-post (which won) left the notification showing with its id forgotten, so the later resume cancel found nothing. A deterministic id is recomputed on every cancel, no map state needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…list Both overlays opened without registering a back-stack entry, so Android's back button skipped them and ran the underlying screen's back action: the image viewer left the conversation for the home view (viewer still open), and the account list stayed open over the profile screen. Register both via the existing pushBack/popBack convention: - image viewer (previewer.js): pushBack on open, popBack on close. - My Profile account list (profileSwitcher): pushBack on open, popBack on close. popBack is idempotent, so closing via the X / backdrop / Escape / a real back-press all keep the stack in sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`---` (or more dashes, plus ***/___) now renders as a divider. Three layers needed fixing, each masking the next: - message-row.js: the content-append guard counted text/img/video but not <hr>, so a divider-only message's span was dropped entirely (rendered nothing). - styles.css: an <hr> inside the inline .dmsg-text had a zero-width containing block and collapsed; pin a visible block width (expands with surrounding text). - markdown.js: disable setext headings so "text" then "---" renders as text + a divider instead of turning the line above into a heading (Vector uses # headings). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… send) Community typing was wired end to end except two gaps: - realtime.rs: COMMUNITY_TYPING (3311) was missing from the realtime sub filter, so typing signals were published (start_typing already routes hex channel ids to send_community_typing) but never received. Add it. Like presence, typing takes the no-persist dispatch branch, so it adds no DB writes per signal. - main.js: the message_new typing-clear filtered active_typers by chat.id, which is the contact npub for DMs but the channel hex for communities, so it never matched and the indicator lingered the full 30s window. Resolve the sender's npub and refresh the header/preview immediately so it clears the instant their message lands. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tplace failures Untrusted-content audit (HIGH batch): - marketplace: validate the attacker-controlled d-tag app id (length + charset, no traversal) at parse and at install/update/uninstall/check_installed - emoji decode + image cache: SSRF guard + streamed byte caps (1 MB emoji, 10 MB avatar/banner) that hold for chunked / no-Content-Length responses - site metadata: hard 512 KB ceiling on the </head> scan (lossy decode) - get_or_cache_image propagates the failure reason instead of returning Ok(None) Emoji failures: - picker/panel hide oversized/unavailable emoji; inline shows a tappable :shortcode: chip explaining why; the editor shows a removable broken cell that explains + guides re-upload; reactions keep their glyph Marketplace icons: - placeholder centers in the slot, CSP-safe fallback, and self-heals (re-fetches) after a manual cache wipe Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The shared .context-menu had `padding: 4px 0`, leaving a slice above the first / below the last item on every showContextMenu dropdown (message right-click, chat/wallpaper menu, picker long-press). Set to 0 to match .profile-more-dropdown; items keep their own padding and the rounded corners clip the first/last hover. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the flat popupConfirm badge popup with a tilting, holographic card. - Tilt by capability: native device-tilt on Android (gravity/accelerometer via a JS bridge; baseline = first reading; low-pass + deadzone), mouse on desktop, finger-drag as the universal fallback - White specular sheen that sweeps with the tilt; spring open/close; reduced-motion aware - Perks section (V for Vector shows its emoji-pack bonuses) - Sensor runs only while the card is open: stopped on close + background, re-armed on resume; Android back button + Escape + backdrop all close it Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ead server A Cloudflare/origin gateway timeout (524/522/520/504) on a large upload won't resolve by retrying the same server, so route to the next Blossom server immediately rather than burning the per-server retries (~2 min each). + a parse test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Register the mini profile with the nav stack (pushBack/popBack) so hardware Back closes the popup instead of navigating the page beneath it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On-device the gravity X/Y components are effectively swapped vs the naive mapping: Y tracks forward/back tilt, X tracks side-to-side. Drive the card's forward/back lean from Y and side-to-side from X (Z / screen rotation stays ignored), so the card now tilts to physically match the phone. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sending a file attachment now shows "Uploading · X/s" where the size sits, mirroring the download-speed readout and computed from the cumulative bytes the backend reports (bytesSent on attachment_upload_progress). Cancelling an upload is now reachable from the hover toolbar (warning-colour x) and the right-click / long-press menu, both gated to attachment uploads (plain text sends have no cancel path). The speed tracker is torn down on completion, failure, and cancel, and never spins for uploads without a file-box rate target. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Some recent additions (distorted face, fight cloud, ...) shipped without a curated shortcode, so they never normalised to a :code:. The runtime EmojiDEX pass now derives one from the display name (lowercase, non-alphanumerics to underscores) for any entry missing it, covering the 9 current gaps and future Unicode additions. The curated shortcode still wins when present. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Share sheet: - Route every entry point (cold-start poll, live event, resume hooks, and a re-poll right after init completes) through the backend's atomic get_pending_share, so a foreground/resume share is handled exactly once and isn't stranded until a later tap (which previously yanked the user back to the chat list mid-navigation). - Reveal Send when shared text pre-fills the composer (a programmatic value set fires no input). Unread badge: - Re-derive the DB-sourced chat-list count when a message is removed, so deleting an unread message drops the in-app badge, not just the OS one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Favorites section was permanently hidden (category button + section had `hidden` with nothing to un-hide them) and its only writer was a right-click handler that saved to localStorage with a brief gold flash but never displayed anything. Dead weight, and localStorage made it global-across-accounts unlike the per-account SQL settings KV. Drop the whole path: arrFavoriteEmojis, toggleFavoriteEmoji, loadFavoritesSection, the contextmenu handler, and the markup/CSS. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapses ~40 hand-inlined `nickname || name || npub` resolutions into one shared getName(profileOrId) helper that also consults the Nostr `display_name` field. Fixes the divergence where a profile that set only `display_name` (no `name`) showed their real name in reactions but their npub everywhere else, author, @mention, profile view, chat header, chatlist. Group/community names and the profile-edit placeholder are left as-is (not person names). The reaction code now uses the shared helper too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bios already turn bare npubs into mention chips, but a pasted vectorapp.io/profile/<npub> link stayed plain text — the bare-npub regex skips an npub sitting right after a `/`. Add a vectorapp.io-specific branch to renderMentions' allowBare pattern, tried first so it claims the whole URL. Scoped to that domain only; other URLs (and other vectorapp.io paths) are untouched, and messages still linkify their URLs as before. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…text
The attachment reply preview was display:flex with no align-items, so the type
icon and its label ("Picture", "Video", ...) aligned to the top and the text sat
high. Center them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Top stop #232527 -> #171717 for a deeper, less grey card backdrop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the tail-anchored offset pager (O(distance-from-now); it loaded 2000+ messages just to reach a week-old read marker) with a random-access model. - Anchored loading: new get_messages_around(chat, anchor, before, after) runs two bounded (created_at, received_at, rowid)-cursor queries, O(window) at any depth. The tuple cursor pages PAST same-timestamp bursts (closes the DB-read half of #300). save_message switched to an UPSERT so re-saves (reactions, edits) preserve rowid and the cursor can't silently drop burst rows. - DOM windowing: bound the rendered rows (~80) over a non-congruent, tail-pinned RAM cache. The buffer may hold [seek-region, GAP, tail]; the newest 500 rows are never evicted, so chat-list preview/sort always see the true latest. The scroll-extends, jump-to-bottom and reply-jump are all gap-aware. - Jump-to-unread: a pill plus a "NEW" divider anchored to the last-read row that survives the scroll-up trim (manual scroll-up reveals it too). Join/leave system-event reveal re-wired into the windowed scroll-up path. - Polish: viewport-fill on open (short messages no longer leave the view unscrollable), intent-aware scroll pin (a slight scroll-up no longer yanks you back during sync), and a media-settle re-scroll on jump-to-bottom. Backend hardening: SessionGuard on the new and existing message-window commands and the realtime/headless mark-read paths (multi-account swap safety); a window clamp on get_messages_around (hostile-frontend DoS); community accept reordered to claim, reset floors, then history-start. Fixes folded in: banned-on-join no longer destroys the UI (openChatlist hides the chat view) and shows a toast; the back-chevron unread dot now matches the chatlist rows; avatarless mini-app lobby players fall back to the default avatar. Verified by two deep code reviews (every Critical/High/Medium/Low resolved), 965 vector-core tests, and a new independent gap-state-machine fuzz harness (tests/event-cache/, run via npm run test:cache) that loads event-cache.js unmodified and asserts the cache invariants across every transition. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A reply to a media message whose target isn't loaded in memory showed a generic "Attachment" in the quote, while in-memory targets already showed the type. The backend-resolved reply context only carried a has_attachment bool. Thread the replied-to attachment's extension through the reply-context resolution: get_reply_contexts now reads the stored kind-15 event's "attachments" tag JSON (each entry carries its own extension) into a new Message.replied_to_attachment_extension, and the quote renderer maps it through getFileTypeInfo so off-screen replies show "Picture"/"Video"/etc. with the right icon instead of the generic label. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The debounced refreshUnreadCounts only re-rendered the UI when no chat was open, so a background message updated chat.unread but never re-evaluated the in-chat back-chevron dot, leaving it dark despite the OS badge. Refresh the dot (it reads the chat.unread just updated) when a chat is open; the chatlist rows still refresh otherwise. Keeps the DB-authoritative chat.unread source, without the diverging unread walk returning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…surface - concord2_* tables (migration 63) + TRANSPORT_CONCORD2 dedup discriminator - v2 service: create/boot-sync/paging/send/typing/metadata/leave/dissolve, authors-routed realtime subscription and dispatch, SessionGuard throughout - Tauri commands branch by id: create_community now founds v2 ONLY; list/get combine both generations; send/sync/typing/leave/delete/metadata route v2 - subscription loop seams v2 wraps by plane-author match before the DM path - session-swap clears v2 routes; boot orphan sweep keeps v2 channels
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is a few prompts of Fable 5, which I have not tested at all. This is an idea of how it might look.