Commit Graph
15692 Commits
Author SHA1 Message Date
Vitor PamplonaandGitHub 418ea3fc46 Merge pull request #3362 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-25 12:04:51 -04:00
vitorpamplonaandgithub-actions[bot] 49e56da1aa chore: sync Crowdin translations and seed translator npub placeholders 2026-06-25 16:01:36 +00:00
Vitor PamplonaandGitHub 204a8e00ee Merge pull request #3364 from vitorpamplona/claude/modest-lovelace-35fe6k
Fix bottom sheet grabber positioning and add console toggle state
2026-06-25 11:59:24 -04:00
Claude 3836cf4b88 fix: console UI — switch in top sheet, grabber at panel top when open
- TopControlSheet: replace SheetItem with SheetSwitchItem for the
  console row, adding a Switch that reflects the current
  consoleShowing state; add consoleShowing param and wire it from
  EmbeddedTabLayer
- BottomConsoleSheet: move the grabber Column before AnimatedVisibility
  so it sits at the top of the expanded panel (standard bottom-sheet
  handle behaviour) and animates upward as the console opens

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GxQdArv6h6SezciUWeoR38
2026-06-25 15:24:23 +00:00
Vitor PamplonaandGitHub bdd8041776 Merge pull request #3363 from vitorpamplona/claude/great-babbage-d0hgki
Reorganize navigation drawer and settings menu items
2026-06-25 10:23:06 -04:00
Claude 8978e0d023 feat: reorder settings sections and drop API-30 gate on drawer Browser
Reorder Account Settings: group network (relays/sync/follows, media/nests),
interactions (reactions/zaps), media playback, identity (DVMs/badges/payments),
then safety/i18n.

Reorder App Settings: group UI (privacy/prefs), composition, personalization
(home/reactions/bottom-bar/profile), then advanced (calendar/OTS/namecoin).

Also drop the now-redundant Build.VERSION_CODES.R gate on BROWSER in
DrawerNavigateItems, matching the fix landed in main (PR #3361).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdbTQuhuk2g5XGWEqmKLRz
2026-06-25 14:09:53 +00:00
Vitor PamplonaandGitHub 156f205801 Merge pull request #3359 from vitorpamplona/claude/quirky-fermat-cwxjy6
Add theme preference support to napplet/browser WebViews
2026-06-25 10:07:53 -04:00
Claude dfe74b983e style: apply spotless formatting to NavBarItem.kt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdbTQuhuk2g5XGWEqmKLRz
2026-06-25 13:54:59 +00:00
Claude b4a68f4b78 feat: reorder drawer menu items
Move BROWSER to the Navigate section (between VIDEO and DISCOVER).
Reorganise Feeds into logical clusters: content types first, then
live/social, communities/chats, calendar, apps/tools, packs, misc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdbTQuhuk2g5XGWEqmKLRz
2026-06-25 13:54:59 +00:00
Claude 46ecde05f2 chore: merge main — resolve PreviewUrl conflict, prefer imported Route
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0198rKcuv32DEoUPpLqsBYbx
2026-06-25 13:53:39 +00:00
Vitor PamplonaandGitHub 7e0bc87dc7 Merge pull request #3361 from vitorpamplona/claude/browser-default-new-users-o5t3kw
Remove API 30+ requirement from Browser feature
2026-06-25 09:50:54 -04:00
Claude 955688c3df chore: spotless import reordering from main merge
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0198rKcuv32DEoUPpLqsBYbx
2026-06-25 13:38:15 +00:00
Claude 8ea1dcb02f fix: drop the API-30 gate on the Browser tab
The Browser tab is a launcher: each opened site loads full-screen in its
own direct-WebView NappletBrowserActivity, which has no API-30 dependency
(it never uses SurfaceControlViewHost and only feature-detects WebView
capabilities). The API-30 gate genuinely belongs to the *embedded*
favorite-app tabs (NappletBrowserService → privacy-sandbox surface), so
keep FAVORITE_APPS gated and remove the gate from BROWSER:

- BROWSER is now unconditional in the default bottom bar and the drawer.
- BrowserScreen no longer shows the "unsupported" fallback below API 30.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WRdmDri69mLJiHpJQpJU73
2026-06-25 13:29:25 +00:00
Claude c5193d268e chore: merge main — keep both theme and isFavorite params in NappletBrowserActivity
Resolved conflicts from main adding isFavorite to NappletBrowserActivity.intent()
and FavoriteAppLauncher.launchUrl() while our branch added the theme parameter.
Both params are now present together.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0198rKcuv32DEoUPpLqsBYbx
2026-06-25 13:28:25 +00:00
Vitor PamplonaandGitHub c1da1e3ce1 Merge pull request #3360 from vitorpamplona/claude/confident-bohr-fgk8h8
Clean up imports: organize and remove unused declarations
2026-06-25 09:23:02 -04:00
Claude 156aea632c fix: spotless import ordering and remove fully-qualified class name
Move java.* imports after kotlinx.* imports in MainActivity and
BouncingIntentNav per ktlint ordering rules. Replace fully-qualified
Route reference in PreviewUrl with an import.
2026-06-25 13:22:04 +00:00
Vitor PamplonaandGitHub db13633fcc Merge pull request #3315 from LubuSeb/lubu/url-comments-304
Add URL-scoped comment feeds
2026-06-25 09:13:19 -04:00
Claude e62f7a4b30 feat: enable algorithmic darkening for web content in napplet WebViews
Adds WebSettingsCompat.setAlgorithmicDarkeningAllowed(true) to all four
WebView setup functions (NappletHostActivity, NappletHostService,
NappletBrowserActivity, NappletBrowserService) so web pages that do not
implement prefers-color-scheme are algorithmically darkened when the
process is in night mode, matching the user's chosen theme.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0198rKcuv32DEoUPpLqsBYbx
2026-06-25 13:08:56 +00:00
Vitor PamplonaandGitHub 2f240e0b87 Merge pull request #3358 from vitorpamplona/claude/always-on-service-logo-t2d2zy
Use hollow gem icon for relay service notification
2026-06-25 08:53:54 -04:00
Claude daf6a2d022 fix: resolve SYSTEM theme to concrete DARK/LIGHT before crossing process boundary
Instead of passing the raw ThemeType name ("SYSTEM") to the :napplet process,
resolve it to the actual dark/light value in the main process by reading
context.resources.configuration.uiMode before the launch. The napplet process
now always receives "DARK" or "LIGHT" and sets UiModeManager.nightMode
unconditionally, making prefers-color-scheme reliable on all ThemeType choices.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0198rKcuv32DEoUPpLqsBYbx
2026-06-25 12:53:03 +00:00
Vitor PamplonaandGitHub 4b71649fcf Merge pull request #3357 from vitorpamplona/claude/fervent-darwin-r8i6dw
Add favorite toggle to web browser and embedded napplets
2026-06-25 08:45:00 -04:00
Vitor PamplonaandGitHub dda8f2d3f6 Merge pull request #3354 from vitorpamplona/claude/funny-clarke-nrzpq4
Extract MintPickerItem composable in CashuWalletScreen
2026-06-25 08:41:19 -04:00
Vitor PamplonaandGitHub 09ee225bf7 Merge pull request #3356 from davotoula/fix/note-grid-layout-issues
Fix/note grid layout issues
2026-06-25 08:38:00 -04:00
davotoula 020c08d561 Code review:
- reuse mediaSizingModifier in GifVideoView
2026-06-25 13:32:56 +02:00
Claudeanddavotoula 2ec072419a fix: keep animated media inside its gallery grid cell
Animated GIF/AVIF media in the multi-image gallery rendered with its own
intrinsic aspect ratio via fillMaxWidth(), ignoring the ContentScale.Crop
the grid asks for
2026-06-25 13:29:06 +02:00
davotoula d71c035669 fix(napplet): log failed temp-file cleanup in NappletBlobCache.put 2026-06-25 12:05:13 +02:00
David KasparandGitHub 67ffbaec86 Merge pull request #3355 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-25 09:21:57 +01:00
davotoulaandgithub-actions[bot] 8d036e0ac0 chore: sync Crowdin translations and seed translator npub placeholders 2026-06-25 08:12:20 +00:00
David KasparandGitHub 0f587eb918 Merge pull request #3351 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-25 09:09:55 +01:00
Claude 50138fe6cb feat: distinct status-bar icon for the always-on relay service
Notification small icons are rendered by the system as flat, alpha-only
silhouettes, so the always-on relay-connection service showed the exact
same solid gem as real notifications (DMs, zaps, mentions). The persistent
service therefore kept reading as a fresh notification.

Add an outlined (hollow) gem variant, `amethyst_service`, and point the
foreground service notification at it so it's clearly distinguishable at a
glance while staying on-brand.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141R1Km7YsA2miFoxLkXLQk
2026-06-25 03:27:36 +00:00
Claude 5662c89d11 fix: extract ConsoleLogRow to fix Kotlin compiler OOM in BottomConsoleSheet
The Text() calls inside the logs.forEach loop were 8 lambdas deep
(Box > Column > AnimatedVisibility > Surface > Column > Column > forEach > Row),
which caused java.lang.OutOfMemoryError in the Kotlin bytecode optimizer
(ConstantConditionEliminationMethodTransformer). This broke CI when
PR #3353 merged. Extract the Row content into a top-level ConsoleLogRow
composable to reduce nesting depth.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmiRNcMEync2rVVbrPzWt
2026-06-25 02:45:39 +00:00
Claude fd1c41b8bb feat: default bottom bar to Home, Messages, Wallet, Browser, Notifications
Change the default bottom navigation for new users from
Home/Messages/Shorts/Discover/Favorite Algo Feeds/Notifications to
Home/Messages/Wallet/Browser/Notifications. The Browser entry is gated
to API 30+ since it renders a cross-process surface, matching the
drawer's existing gating.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WRdmDri69mLJiHpJQpJU73
2026-06-25 02:40:03 +00:00
Claude 85388e3c4b feat: pass theme preference to napplet browser and activity surfaces
The :napplet sandboxed process does not share memory with the main process,
so UiModeManager night mode set in the main app does not propagate there.
Pass the user's ThemeType name ("DARK"/"LIGHT"/"SYSTEM") via Intent extras
and Messenger IPC bundle keys, then apply UiModeManager.nightMode in each
receiving surface (NappletHostActivity, NappletBrowserActivity,
NappletHostService, NappletBrowserService) before any views or WebViews are
created. This makes prefers-color-scheme and the activity chrome match the
user's chosen theme in all napplet/nsite/web-app surfaces.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0198rKcuv32DEoUPpLqsBYbx
2026-06-25 01:38:11 +00:00
Claude d33d1e36fd feat: move favorite to pull-down sheet, remove from OmniBar
Remove the star icon from the browser URL bar (OmniBar) and add a
favorite toggle row to both pull-down sheet surfaces instead:

- Compose TopControlSheet (embedded web tabs and napplets): shows
  filled/outline star with "Add to favorites" / "Remove from favorites"
  sourced from FavoriteAppsRegistry; isFavorite state flows reactively
  through EmbeddedTabChrome so the label updates without reopening.

- Native NappletControlSheet (full-screen NappletBrowserActivity): same
  toggle backed by a new MSG_TOGGLE_WEB_FAVORITE IPC message handled in
  NappletBrokerService; initial state is passed via intent so the star
  opens in the correct filled/outline state for the launch URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBpBE7bQJ2JRni6sYG8UDo
2026-06-25 01:16:06 +00:00
Claude fe392402c3 fix: extract MintPickerItem to fix Kotlin compiler OOM in MintPicker
The deeply-nested Text() call inside DropdownMenu > forEach > DropdownMenuItem
generated bytecode with MAXSTACK=26, which caused java.lang.OutOfMemoryError in
the JVM bytecode frame analyzer (ConstantConditionEliminationMethodTransformer).
Extracting DropdownMenuItem into a top-level private composable breaks the lambda
nesting depth and lets the compiler succeed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmiRNcMEync2rVVbrPzWt
2026-06-25 01:03:29 +00:00
vitorpamplonaandgithub-actions[bot] 443bfba65c chore: sync Crowdin translations and seed translator npub placeholders 2026-06-25 00:31:29 +00:00
Vitor PamplonaandGitHub b1aef7d6ac Merge pull request #3353 from vitorpamplona/claude/serene-hypatia-48qdoz
Add JavaScript console logging to embedded and full-screen browsers
2026-06-24 20:29:33 -04:00
Vitor PamplonaandGitHub 2d1e9a9e1f Merge pull request #3352 from vitorpamplona/claude/eloquent-shannon-0fzkag
Implement tiered memory trimming with debug UI and feed size limits
2026-06-24 18:21:12 -04:00
Claude f2f80cd88d fix: stop clearing Robohash/richtext caches on every app background
TRIM_MEMORY_UI_HIDDEN fires on every app switch, not only under memory
pressure. Clearing Robohash (CPU-intensive SVG assembly) and
CachedRichTextParser to zero there forces a full rebuild on every
resume, causing visible jank.

Restructure the trim tiers to match Android's intent:

  UI_HIDDEN  (20) — just backgrounded, no pressure:
      trim Coil image cache to 1/2 only; leave parsed-text and
      avatar caches warm so resume is instant.

  BACKGROUND (40) — mild background pressure:
      trim images to 1/4, richtext→100, robohash→20, nip11→200.

  MODERATE   (60) — system is hurting:
      clear images, richtext→50, robohash→10, nip11→100.

  COMPLETE   (80) — kill imminent:
      clear everything including nip11.

Foreground levels (RUNNING_LOW/RUNNING_CRITICAL) are unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019h2c44rAwexuUEP3kky2F3
2026-06-24 22:00:52 +00:00
Claude ea2dbd9a77 fix: keep lastNotes intact on CardFeedContentState.trimToSize()
Clearing lastNotes caused the next additive update to call
refreshSuspended(), reloading the full filter limit (~500 notes) from
LocalCache and immediately undoing the trim.

With lastNotes intact the fast additive path stays active: only
genuinely new notifications are appended, so the card list remains near
maxItems until the next full feed key change or navigation event.

The Note refs in lastNotes are the same object instances already held
by LocalCache. They are freed when MemoryTrimmingService prunes
LocalCache and the following refreshSuspended() replaces lastNotes with
the pruned set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019h2c44rAwexuUEP3kky2F3
2026-06-24 21:42:14 +00:00
Claude e17c95eda1 fix: clear lastNotes on CardFeedContentState.trimToSize() to release Note refs
CardFeedContentState.lastNotes holds strong references to every raw Note
that passed the notification filter, used for additive dedup
(filteredNewList.minus(lastNotesCopy)). Trimming only the Card list left
all those Note objects pinned — they couldn't be GC'd even if LocalCache's
SoftCache had evicted them.

Fix: clear lastNotes/lastAccount alongside the Card list truncation.
The next additive update finds lastNotes == null, skips the fast path,
and calls refreshSuspended() — one controlled rebuild from LocalCache
that also resets the dedup set to the current state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019h2c44rAwexuUEP3kky2F3
2026-06-24 21:35:14 +00:00
Claude 101ca698d9 fix: polish BottomConsoleSheet UI
- Show source filename and line number as dimmed secondary text on each
  log entry (matches native NappletConsolePanel behaviour)
- Replace hardcoded #FF9800 orange with a dark-mode-aware amber pair:
  #E65100 in light mode, #FFB74D in dark mode
- Cap panel height at 40% of screen height (was fixed 240dp) so large
  phones display more log lines

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QXpdfwj2cujnEa7HPzQXj
2026-06-24 20:57:07 +00:00
Claude 57a63bd4b2 fix: stop wiping relay info cache on every app background
At UI_HIDDEN (fires on every app switch), nip11Cache.trimToSize(0)
was clearing all successfully-fetched NIP-11 relay documents, forcing
10–50 redundant HTTP fetches on the next foreground resume.

- Change UI_HIDDEN trim target from 0→100; the 100 most-recently-used
  relay docs are kept across a background/foreground cycle.
- Stop trimming relayInformationEmptyCache in Nip11CachedRetriever:
  it holds only lightweight display-name+favicon-url placeholder objects
  (no network data), so trimming saves negligible memory but silently
  re-triggers NIP-11 HTTP fetches for every relay on resume.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019h2c44rAwexuUEP3kky2F3
2026-06-24 20:44:00 +00:00
Claude 45f1fd91a3 feat: add console.log panel accessible from top pull-down control sheets
Captures JavaScript console output (log/warn/error/debug) from embedded
WebViews and surfaces it via a bottom pull-up sheet, triggered by a new
"Console (N)" row in the existing top pull-down control sheets.

Embedded browser (Compose):
- NappletBrowserService: attaches WebChromeClient to intercept console
  messages and forwards them to the client process via new MSG_CONSOLE_LOG
  IPC message in NappletBrowserContract
- EmbeddedBrowserController: implements new ConsoleBridge interface,
  stores up to 200 entries in a SnapshotStateList observable by Compose,
  handles the incoming IPC message
- TopControlSheet: adds optional consoleCount/onConsole params; shows a
  "Console (N)" row when onConsole is provided
- BottomConsoleSheet: new Compose pull-up panel anchored at the bottom,
  with level-coloured monospace log entries and a Clear button
- EmbeddedTabLayer: wires ConsoleBridge → TopControlSheet → BottomConsoleSheet

Full-screen activity browser (native Views):
- NappletBrowserActivity: attaches WebChromeClient, wires NappletConsolePanel
  and updates the control sheet count label on each new entry
- NappletControlSheet: adds optional onConsole callback and updateConsoleCount()
- NappletConsolePanel: new native-View bottom pull-up panel with scrollable
  log entries, grab-to-open gesture, and Clear button; capped at 200 entries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QXpdfwj2cujnEa7HPzQXj
2026-06-24 20:34:43 +00:00
Claude 5435345f7d feat: trim richtext, robohash, and relay-info caches under memory pressure
Add trimToSize(maxItems) to:
- CachedRichTextParser: trims richTextCache (500) and isMarkdownCache
  (200) proportionally
- CachedRobohash: trims the ImageVector LruCache (100)
- Nip11CachedRetriever: trims both the document and empty-placeholder
  caches (1000 each)

Wire all three into AppModules.trim() tiered by OS pressure level:
  RUNNING_LOW      → 50% capacity
  RUNNING_CRITICAL → 20% capacity
  UI_HIDDEN+       → evict all (app not visible, safe to clear)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019h2c44rAwexuUEP3kky2F3
2026-06-24 20:11:28 +00:00
Claude 8407684d9d feat: trim Coil memory cache and ExoPlayer warm pool under memory pressure
- ExoPlayerPool.releaseWarmPool(): evicts all paused-with-buffer (warm)
  players back to the cold pool. Safe under pressure because warm players
  are idle; active (checked-out) players are never in either pool.

- PlaybackService.onTrimMemory(): when level >= RUNNING_CRITICAL, drains
  both pool instances' warm slots via releaseWarmPool(). The Service
  receives onTrimMemory() directly from Android so no routing through
  AppModules is needed.

- AppModules.trim(): trims Coil's in-memory image cache proportional to
  OS pressure level:
    RUNNING_LOW  → trimToSize(maxSize / 2)
    RUNNING_CRITICAL → trimToSize(maxSize / 4)
    UI_HIDDEN+   → trimToSize(0)  [app backgrounded, safe to clear]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019h2c44rAwexuUEP3kky2F3
2026-06-24 20:11:28 +00:00
Claude 5b5bc5c70c fix: raise critical memory feed trim size from 50 to 200
50 was too aggressive — users would lose most of their scroll history on
any critical-pressure event. 200 is a better balance: still releases
~60% of the 500-note strong references per feed while keeping a
reasonable amount of history visible without a reload.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 20:11:28 +00:00
Claude 057b6266d9 feat: trim feed lists to 50 items under critical memory pressure
Feed lists hold ImmutableList<Note> references. At 40+ feeds × 500 notes
each, that's 20k strong Note references that prevent GC from collecting
objects that LocalCache has already pruned. Under RUNNING_CRITICAL the
feeds are shrunk to 50 items, releasing ~19k references per account.

Implementation:
- FeedContentState.trimToSize(maxItems) — truncates the loaded list in-place
- CardFeedContentState.trimToSize(maxItems) — same for notification card feeds
- AccountFeedContentStates.trimFeedsToSize(maxItems) — fans out to all feeds
- AppModules.trimLevelEvents: SharedFlow<Int> — broadcasts the OS level
- AccountFeedContentStates subscribes and calls trimFeedsToSize(50) at
  RUNNING_CRITICAL, letting the next scroll/refresh repopulate from cache

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 20:11:27 +00:00
Claude 4eb195fa5e fix: reorder pruning tiers in MemoryTrimmingService
Tier 2 (medium, >= RUNNING_LOW): pruneHiddenEvents + pruneHiddenMessages
  — muted/blocked content is known-safe to drop at low pressure.

Tier 3 (critical, >= RUNNING_CRITICAL): pruneOldMessages + pruneRepliesAndReactions
  — these are more aggressive since they remove content the user may
  still scroll back to, so reserve them for genuine memory emergencies.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 20:11:27 +00:00
Claude 281955fb17 fix: move cleanObservers to Tier 1 in MemoryTrimmingService
cleanObservers() only removes flows not currently held by the UI, so
it carries no visible side effects and is safe to run on every trim
regardless of pressure level. Move it from Tier 3 (RUNNING_CRITICAL)
to Tier 1 (always) so unused observer links are freed even on mild
memory signals.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 20:11:27 +00:00