91 Commits

Author SHA1 Message Date
4dab3a4ba4 TASK-069: rows keep their branch while the repository list reloads
Going between the two repository tabs sent every row back to "checking"
and then to its branch chip, which changed each row's height and the
lines of its text twice per move. The view model keeps the branch last
found for each repository and starts a reloaded list with it, still
checking for a newer one.

Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:26:22 +09:00
e1abdfc4ca TASK-069: a move no longer starts by drawing every screen again
Measured on the product owner's phone over adb. The code workbench value,
provided through a static composition local, was rebuilt on each root
recomposition, which turned skipping off for all four tab screens on every
move. It is remembered now. Looping animations stop while their page is off
screen, the two repository lists stay composed side by side, and a
repository's branches open beside the list instead of replacing it.

First frame of a bottom tab move: 80-118 ms to 23-34 ms. The branches
slide has not been measured on the device yet.

Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:15:07 +09:00
db8fc0574e TASK-069: screens push in, repository tabs hold their list, tabs stay composed
Opening a screen pushes it in from the right and Back reverses it, in place
of a 90 ms fade that read as a flicker. The repository tabs keep each tab's
last list through a reload, so nothing settles after the glide. The four
bottom tabs stay composed; only the one shown answers Back.

Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:38:54 +09:00
d9baf04a1d TASK-075: the exit question stands alone
The line under it explained what the two buttons already say.

Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:34:51 +09:00
a140b56aec TASK-075: chip border, quiet short loads, exit question, back to top
A selected filter chip is outlined; the loading cup waits 450 ms before it
shows; Back on 홈 asks before leaving; long lists offer a button back to the
top; AnimatedContent no longer animates its own size, which made sideways
moves drift diagonally.

Agent: main (opus), implementer (sonnet) for Chips.kt and ExitConfirmDialog.kt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:29:41 +09:00
bb546421fc TASK-069: bottom tabs glide past the tabs between
The four tab screens are pages of one pager that a tab press scrolls, so 홈
to 알림 passes 저장소 and 작업 instead of sliding straight there. Swiping is
off; entering and leaving other screens is unchanged.

Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:21:42 +09:00
a28feb1a47 TASK-069: the first screen fades in under the cup instead of gliding
Until the session is known the route shown is the repository list; when it
becomes Home the change played the full-width tab glide just as the launch
cover was fading. That hand-over is now a plain fade, and the cover waits for
it (at most 1.5 s longer) before it leaves.

Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:16:17 +09:00
0f96a8ed76 TASK-074: version 0.2.4 for the build published to the team's Gitea
Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:54:09 +09:00
7d41e89c99 TASK-073: the app fetches and installs its own update, without a file
"업데이트" streams the published APK into a system install session instead of
opening the browser, so nothing is saved to the phone. Progress, cancel and
retry are shown. Android 12+ is asked to skip its confirmation, which it does
once this app is the installer of the copy being replaced.

Adds REQUEST_INSTALL_PACKAGES and UPDATE_PACKAGES_WITHOUT_USER_ACTION.
Not run on a device.

Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:51:08 +09:00
60016d1537 TASK-072: version 0.2.3 for the build published to the team's Gitea
Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:42:41 +09:00
1f6f0dac8a TASK-069: tabs inside a screen glide like the bottom tabs
The repository list (all / favourites) and a repository's issues and pull
requests keep their header and tab row still while the list under them moves
the whole width. TabGlide in Motion.kt holds the movement.

A repository tab change reloads through Loading, which names no tab, so the
screen remembers the tab that was tapped and keeps the tab row up meanwhile.

Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:37:00 +09:00
7368b27c65 TASK-069: bottom tab switches glide the whole width
Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:28:44 +09:00
dd90f6932b TASK-071: version 0.2.2 for the build published to the team's Gitea
Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:04:50 +09:00
a9f08ebbdf TASK-070: update check and download read the team's Gitea
The old download page asked for a password and is no longer used. The
app now reads version.json and fetches UBGitea.apk from the apk branch.

Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:01:33 +09:00
11bb082f0f TASK-069: in-app motion built on the tea cup
Splash, cup loader, list skeleton, empty-screen cup, pull to refresh
indicator, star and tab icon spring, screen and tab transitions, and a
row by row diff intro. Compose animation APIs only.

Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:34:42 +09:00
e47ad799f1 TASK-068: app icon shows a small tea cup instead of the branch
Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:19:13 +09:00
4b03c52975 TASK-067: version 0.2.1 for the build published to the team's Gitea
Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:03:53 +09:00
7aa79f15ce Merge TASK-065 and TASK-066 into the reviewed branch
Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 12:59:35 +09:00
69556b1e23 TASK-066: download page and in-app update check
- version 0.2.0 (versionCode 2)
- The app reads version.json from the download page once at launch and
  offers the page when a newer build is out; settings shows the version
  and a check button.
- tools/publish-download.py encrypts an APK under the download password
  and writes the page that decrypts it in the browser.

Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 20:52:45 +09:00
54530096e0 TASK-065: code tabs, comparing two files, and reading aids
- Up to five code files saved as tabs in a strip above the bottom tabs.
- Compare a file with one from another ref, commit or place; the
  current file goes left or right, then the other is chosen.
- The side pane on a wide screen is dragged to size and snaps back to
  its default; the header/source split snaps to the middle.
- A tapped name is marked everywhere in the file, with previous/next.
- The Repositories tab pressed again goes back to the list page the
  repository came from, then to the first page.

Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 20:38:55 +09:00
6a149087c0 TASK-064: ignore a second star tap in flight; keep -- and ++ lines in diffs
Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 20:25:49 +09:00
17fa248065 TASK-061: edge tab for the side pane, and a new app icon
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 16:31:27 +09:00
023fa0b728 TASK-060: a function's name in the code is a link to its counterpart
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 16:24:26 +09:00
aa23f356d9 TASK-059: jump from a function in the outline to its counterpart file
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 16:17:22 +09:00
b663f24009 TASK-058: outline for C++ with CRLF line endings, macros and #if blocks
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 16:07:46 +09:00
5cc11d8420 TASK-057: side pane cards, in-place folder tree, unfolded Home headline
- The viewer's side pane is two cards: folder structure, and classes and
  functions. A button swaps them and the choice is kept.
- Folders open in place; files inside them open from the tree.
- Unfolded Home: the headline is a card and the columns are equal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 15:44:54 +09:00
fa87a0b32f TASK-056: edge-swipe outline, resizable pair, tree-based directory listing
- Code viewer: the outline opens with a swipe from the left edge and closes
  with a swipe left, with one pane or two.
- Side-by-side .h/.cpp: a draggable handle resizes the panes (20% to 80%).
- Directory listing reads Git tree objects instead of the contents endpoint,
  which looked up the last commit of every entry.
- A listing that needs more than one request shows percent and count.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 15:29:10 +09:00
9046985ba4 TASK-055: first device feedback on the redesign
Repository names get a line to themselves; the repository, work and notification tabs lose their title; a today count on Home opens today's activity of that kind; the outline is a foldable tree of classes and methods without fields; Home can show only starred repositories and remembers the choice; settings has a light/dark/system theme choice.

Whether the app is dark is now the app's decision (UBGiteaTheme.isDark), so the logo moved off a -night drawable. 420 unit tests pass, lint reports 0 errors. Installed on the product owner's Z Fold7.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 15:09:39 +09:00
d1a45abc16 TASK-050..054: tabs, Home, Work, code viewer, C++ pairs, fold layouts
Four tabs in an app shell that becomes a rail from 600dp, a brighter sky-blue palette with the real UbiSam logo, a Home tab built on Gitea's activity feeds, a Work tab that searches issues and pull requests across repositories, a full-screen code viewer with search, outline and Visual Studio colors, C/C++ header/source pairing, and two-pane layouts for wide windows.

The five tasks were built and checked together, so they are one commit: MainActivity, the routes and the shared components belong to all of them. 409 unit tests pass, lint reports 0 errors. Verified on an emulator up to the sign-in screen only; no physical device was available.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 14:35:00 +09:00
105e181bd2 Track assetlinks.json and the technical overview deck
assetlinks.json is the Digital Asset Links statement served from the Gitea host for the OAuth App Link; it holds only the package name and public certificate fingerprints. The deck is rebuilt for the redesign (TASK-050 to TASK-054).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 14:35:00 +09:00
10b38a3ac4 TASK-049: C/C++ highlighting and multi-line comment coloring
Adds a CPP language, preprocessor coloring, hex and digit-separator numbers, and block-comment state carried across lines. CodeBlock now highlights a file once instead of on every recomposition.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 13:17:33 +09:00
d9536289e9 TASK-048: source viewer line numbers, syntax highlighting, word wrap
Includes the two same-day follow-ups that fixed horizontal scrolling. See tasks/048-code-viewer-readability.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 13:17:33 +09:00
fccb817381 TASK-047: favorite-push notification accuracy (branch + commit)
See tasks/047-favorite-push-notification-accuracy.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 13:17:32 +09:00
5caa6057f1 TASK-046: commit history load performance and loading feedback
See tasks/046-commit-history-load-performance.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 13:17:32 +09:00
71c8457578 TASK-045: sky blue + white redesign, UbiSam logo
Rebuilds the Material3 light/dark palette around a sky-blue primary, adds the UBGiteaTopBar wordmark to the repository list, and recolors the launcher icon background to match.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 13:17:31 +09:00
39be1b71dc TASK-044: offline cache for the 전체 저장소 repository list
Closes one of docs/product.md's open product decisions ("whether
repositories must be cached for offline browsing"), at the human product
owner's explicit request.

A new file-backed RepositoryListCache persists the ALL/no-organization-
filter repository list after every successful load, using a dedicated
plain DTO (not the domain RepositorySummary directly, since
RepositoryCoordinates/RepositoryRef validate through a private-constructor
factory Gson's reflection would otherwise bypass). RepositoryListViewModel
falls back to it whenever a live fetch of that view fails with a
network-classified error (cold start or mid-session), seeding
repositorySnapshots so pagination keeps working from the cache. A
page-level banner makes the fallback impossible to mistake for a live
view. The cache is cleared on logout.

Deliberately scoped to just the landing list -- not favorites,
organization filters, or repository internals (branches/files/history) --
confirmed with the human product owner after they live-tested the
fallback via airplane mode and asked about extending it further; decided
to hold that off.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-31 11:05:25 +09:00
fbec743e5f TASK-043: fix empty 즐겨찾기 offering 로그아웃 instead of a way back
The FAVORITES-mode empty state ("nothing starred yet") shared its
MessageState call with the ALL-mode empty state ("account has zero
repositories"), inheriting a hardcoded 로그아웃 action that only made sense
for the latter. Gives FAVORITES-mode its own action: "전체 저장소 보기",
wired to the same onShowAllRepositories the tab row already uses.

Live bug report: "현재 화면 즐겨찾기가 안되어있는데 로그아웃할거냐는
버튼이 왜 있지?" Live-verified the fix on-device.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 22:58:05 +09:00
ca999a7bb8 TASK-042 follow-up: colored status badges on NotificationsScreen
Closes the known limitation from the previous commit: NotificationsScreen
had the identical plain-text state-label gap, in a separate screen/enum
(NotificationSubjectState, which also has an UNKNOWN case). Adds its own
StatusBadge composable with the same color roles, mapping UNKNOWN to the
neutral surfaceVariant treatment VisibilityBadge already uses for private.

Live-verified on-device: notifications 전체 list shows all three real
colors side by side (닫힘/병합됨/열림).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 22:44:00 +09:00
471456258b TASK-042: colored status badges for issue/PR state (열림/닫힘/병합됨)
Replaces the plain-text 열림/닫힘/병합됨 label on issue/PR list rows and
detail headers with a compact colored badge, reusing the VisibilityBadge
pattern TASK-039 established for 공개/비공개. OPEN reuses the app's green
primaryContainer, CLOSED reuses the baseline errorContainer, MERGED reuses
the teal tertiaryContainer -- all existing theme roles, so dark mode is
handled automatically with no new hardcoded colors.

Found while verifying issue/PR body rendering for real content (confirmed
correct, no bug) during the design-review pass. Live-verified on-device in
both light and dark mode.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 22:40:33 +09:00
39cbad522e TASK-041: fix favorite star reverting after switching tabs
toggleFavorite kept starredCoordinatesCache in sync with a successful
star/unstar but never patched repositorySnapshots (the per-mode
full-repository-list cache), so a starred repository's count reverted
to its pre-toggle cached value on the next visit to that mode, and a
newly-starred repository could be silently missing from an
already-cached favorites list.

Fix: two new helpers (adjustCachedStarCounts, removeFromCachedSnapshot)
patch every cached snapshot containing the toggled repository, and a
star invalidates just the FAVORITES snapshot key if already cached.
Chosen over a full cache-clear-and-refetch after live testing showed
Gitea's own starsCount can lag slightly behind a star/unstar on its
plain repository-listing endpoint.

Also fixes a related gap: unstarring from the FAVORITES tab wasn't
updating starredCoordinatesCache at all (only the two ALL-mode
branches were).

Two new regression tests; all existing tests pass unmodified.
Build/test/lint green, live-verified on device.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 19:42:08 +09:00
2b9a6e091e TASK-040: collapse repository-list header buttons into an overflow menu
Replace the three always-visible 설정/알림/로그아웃 OutlinedButtons on the
repository list's top bar with a single hamburger-style overflow menu
("☰" IconButton + DropdownMenu). Same three callbacks, presentation-only
change, no icon library (plain Unicode glyph, matching this app's
established convention).

Build/test/lint green. Not yet live-verified on device — wireless
debugging was intentionally paused for this round per request.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 19:00:13 +09:00
9663d9ab9c TASK-039: design pass — app icon, visibility badge, header truncation, PR label
- Add a custom adaptive launcher icon (background/foreground/monochrome
  layers), replacing Android's bare default. Original hand-drawn git-branch
  glyph on the app's own established primary green, not a reproduction of
  Gitea's logo.
- Add a compact VisibilityBadge for the repository list's public/private
  label, replacing an unstyled full-width text line.
- Truncate the references screen's repository-name header to 2 lines with
  an ellipsis instead of letting a long owner/name wrap to 3 lines and push
  the branch/tag list down.
- Change "Pull Request" -> "PR" in strings_workitems.xml and
  strings_notifications.xml, matching the "PR" abbreviation already used
  everywhere else in the app and fixing a visible line-wrap on the live
  notifications list.

Build/test/lint green, live-verified on device (light and dark mode).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 18:42:51 +09:00
9a8f855e5e TASK-038: Settings screen with opt-in reactive auth retry and favorite-push toggle
Two features gated behind a new Settings screen so neither silently
changes existing behavior:

- Reactive auth retry (opt-in, default off): a live 401 despite
  proactive refresh already judging the token fine now optionally
  retries once with a forcibly-refreshed token before falling back to
  re-login. ReactiveTokenProvider is a separate interface from the
  existing fun interface BearerTokenProvider (not an addition to it),
  so every existing test fake keeps compiling; a new shared
  callAuthenticated helper (used by all four Gitea API classes instead
  of four duplicated retry implementations) checks for it and falls
  back to the old single-attempt behavior otherwise.
- Favorite push notifications (on/off, default on): TASK-036's
  background checker can now be turned off entirely, closing that
  task's own 'no in-app way to disable this' limitation.

Every new constructor parameter defaults to preserving old behavior
exactly, so no existing call site or test needed to change.

Live-verified: Settings screen opens from a new button, both switches
toggle and persist across navigation, no crash.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 18:11:12 +09:00
75f887c497 TASK-028 follow-up: visible feedback for an out-of-range page-jump value
The Issues/PR numbered-pagination page-jump field silently clamped any
typed number to [1, totalPages] only on submit -- typing '999' would
quietly land on the last page with no explanation. The field now shows
isError/supportingText whenever the currently-typed value is out of
range, making the clamp visible before the tap. Submitting still
clamps exactly as before; this only adds visible feedback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 17:41:51 +09:00
683305e6c8 TASK-037: already-starred repositories show filled on the ALL list's first load
Closes a limitation accepted since TASK-018/TASK-030: a repository
already starred before the current session showed outlined until
directly toggled. loadStarredCoordinatesOnce fetches the account's
full starred set once per session (one request for the whole list, not
N+1 per row) via the same /user/starred endpoint already used for the
FAVORITES tab, mirroring loadOrganizationsOnce's caching shape.
toggleFavorite keeps the cache in sync on success; refreshRepositories
and logout invalidate/clear it the same way the equivalent
repositorySnapshots/cachedOrganizations caches already are.

Live-verified: a repository independently confirmed starred via the
FAVORITES tab showed filled on the ALL list immediately after a fresh
app launch, no interaction needed, screenshot-confirmed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 17:36:15 +09:00
18e59d960c TASK-036 follow-up: deep-link the favorite-push notification
Tapping the notification now opens the app directly at that
repository's file browser on its default branch, instead of just the
app's normal entry point. The push-detection step only tracks one
scalar 'latest push time' per repository, not which branch produced
it, so the deep link targets the repository's default branch rather
than the specific pushed branch -- still a correct, useful 'take me to
this repository' action.

ListFavoritedRepositoriesUseCase now returns full RepositorySummary
(not just coordinates) so defaultBranch is available; MainActivity
bridges the notification's PendingIntent extras into its existing
Route/routeStack navigation via a mutableStateOf Activity property read
by a LaunchedEffect, mirroring the existing OAuth-callback consumption
pattern.

Live-verified via a simulated notification-tap intent (adb shell am
start with the exact extras FavoritePushNotifier sets): opened directly
at the target repository's file browser on the requested branch,
screenshot-confirmed, no crash.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 17:26:23 +09:00
b1a00233b0 TASK-036: push notification when a favorited repository gets a new push
Gitea's own notification feed doesn't fire for plain pushes (only
issue/PR activity), so this app now periodically checks each favorited
repository's most recent push time itself and posts a local
notification when it advances past what was last seen.

- FavoritePushDetector: pure comparison logic, never notifies on a
  repository's first sighting (only a real advance counts), avoiding a
  notification storm the first time this feature ever runs.
- GetLatestPushEpochMillisUseCase: extracts the same 'newest branch
  commit time' heuristic RepositoryListViewModel already uses for the
  repository list's own last-push row, as a shared, reusable use case.
- SharedPreferencesLastSeenPushStore: one long per favorited repo, not
  Room (docs/architecture.md reserves Room for offline caching).
- FavoritePushCheckWorker: 15-minute WorkManager periodic job,
  network-constrained, no-ops cleanly with no session or no favorites.
- POST_NOTIFICATIONS requested at runtime on API 33+; a denial
  degrades safely, the worker still runs and updates its baseline.

No deep link from the notification to the specific repository yet --
tapping it opens the app normally. End-to-end delivery (a real push
landing, 15 minutes elapsing) is unit-tested and the scheduling/
permission wiring is live-confirmed (prompt appeared, was granted, no
crash), but an actual notification firing hasn't been observed within
this same live-testing session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 17:15:35 +09:00
9e4cbe3e89 TASK-035: remove redundant per-file Commit button from the code browser
The header's 'Commit' button (onViewHistory) just switched
BranchDetailScreen's pager to page 1 -- the exact same action the
top-level 'Commit' tab's own onClick already performs. No path/file
info ever flowed through it, so it was never actually file-specific
history, just a duplicate shortcut that TASK-031's rename made obvious.
Removed the button, its parameter threading, and the now-unused
codebrowser_history string.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 16:55:21 +09:00
bec12e4f46 TASK-034 third round: make favorite star/count update optimistic
Every state/count change in toggleFavorite was applied only after the
network round trip to this app's home-NAS-hosted Gitea completed, so a
tap visibly did nothing until that finished -- reported as feeling
laggy. The star/count now update synchronously on tap, before the
coroutine launches; a failed request rolls the optimistic change back
instead of leaving the display stuck at it. FAVORITES-list unstar (row
removal) stays non-optimistic, since reverting a removed row is more
jarring than reverting a color/count.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 16:49:44 +09:00
2e8c9e7987 TASK-034 follow-up: repeat tap on a starred row now un-stars instead of no-op
The prior fix for double-counting used an early-return guard that made
a repeat tap on an already-Starred row do nothing -- fixed the count
bug but also made the star impossible to undo from the ALL list.
Replaced it with a real toggle: an already-starred-this-session tap
now calls unstar() and decrements the count back down instead of being
inert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 16:42:17 +09:00