Commit graph

90 commits

Author SHA1 Message Date
systemblue
f8a050e484 fix: refresh the merged notifications list when a push notification is opened (#71)
**Why.** Tapping a push notification bumps `NavigationState.notificationsRefreshTrigger` so the notifications tab reloads on arrival. The single-instance `NotificationsOverviewView` observes the trigger; the merged multi-instance overview never did, so multi-instance users landed on a stale list that still showed the tapped thread as unread until a manual pull-to-refresh.

**What changed.** `MergedNotificationsOverviewView` observes the same trigger with the same `onChange` the single-instance view uses, and reloads.

**Verification.** Full `ForjiTests` suite passes (218 passed / 0 failed, iPhone 17 Pro, iOS 26.5, rebased onto current main). SwiftLint and SwiftFormat clean. The push-open path needs a real APNs round trip, so I couldn't exercise it end-to-end locally; the change mirrors the single-instance wiring line for line.

I grant Stefan Hausotte an irrevocable, worldwide, royalty-free license to use, sublicense, and distribute my contribution, including through Apple's App Store under the project's App Store exception.

Reviewed-on: https://codeberg.org/secana/Forji/pulls/71
2026-06-10 20:17:57 +02:00
Stefan Hausotte
2a679140e6 test: stream seed progress and add a docker exec timeout (#69)
The integration seeder gave no visible progress and could hang
indefinitely. A `docker compose exec` for the admin-user step wedged on
a transient Docker-on-macOS flake, and `DockerExec.run` waited on it
with no timeout, blocking the whole UI test suite forever with no output
(stdout was block-buffered, so the existing phase prints never flushed).

- Line-buffer the seeder's stdout so phase progress streams live instead
  of being dumped all at once when stdout is a pipe.
- Add numbered "[n/7] <phase> (Ns elapsed)" headers for a clear progress
  and timing signal.
- Add a 60s timeout to `docker compose exec` and retry the admin-user
  step, so a hung exec fails fast and recovers instead of wedging the
  suite.

Reviewed-on: https://codeberg.org/secana/Forji/pulls/69
2026-06-09 12:55:45 +02:00
systemBlue
b1942df58d feat: compact notification rows (#55)
## What

Notification rows now put the repository name and timestamp on one line ("repo · time") instead of two stacked caption lines, so the list scans like Mail and fits more per screen. The unread dot drops its `glassEffect` for a plain filled circle (cleaner, and avoids GPU glass compositing on a 10pt element inside a scrolling list).

Before: title / repo / time on three lines.
After: title, then "repo · time", with the repo truncating in the middle and the timestamp fixed-width.

Verified: builds clean (Xcode 26, iPhone 17 Pro simulator), SwiftLint passes, confirmed in the simulator with preview data.

---

I grant Stefan Hausotte an irrevocable, worldwide, royalty-free license to use, sublicense, and distribute my contribution, including through Apple's App Store under the project's App Store exception.

Reviewed-on: https://codeberg.org/secana/Forji/pulls/55
2026-06-09 12:21:05 +02:00
systemBlue
78c91cd2a9 feat: show the overview filter summary only when filters are active (#60)
## What

The Issues and Pull Requests overviews pinned a centered filter-summary caption ("Open") below the title even in the default state, which just duplicated the filter icon. Now the summary appears only when a non-default state or involvement scope is active, so the default view carries no extra chrome and the strip becomes a clear signal that the list is filtered.

Verified in the iPhone 17 Pro simulator: default state shows no strip and no gap, and the filter icon still reflects state. Builds clean on Xcode 26, SwiftLint passes.

---

I grant Stefan Hausotte an irrevocable, worldwide, royalty-free license to use, sublicense, and distribute my contribution, including through Apple's App Store under the project's App Store exception.

Reviewed-on: https://codeberg.org/secana/Forji/pulls/60
2026-06-09 12:18:44 +02:00
systemBlue
ab393d0237 feat: shorten the Pull Requests tab to PRs in repository detail (#59)
## What

The repository detail's segmented control has four segments (Code, Issues, Pull Requests, Actions). "Pull Requests" dominated the width, squeezed "Code", and truncated on narrower iPhones. Shortening it to "PRs" balances the segments and keeps them legible.

Verified in the iPhone 17 Pro simulator (segments now even); builds clean on Xcode 26, SwiftLint passes.

---

I grant Stefan Hausotte an irrevocable, worldwide, royalty-free license to use, sublicense, and distribute my contribution, including through Apple's App Store under the project's App Store exception.

Reviewed-on: https://codeberg.org/secana/Forji/pulls/59
2026-06-09 12:15:52 +02:00
Stefan Hausotte
d580156fa2 ci: add automatic changelog creation 2026-06-04 15:03:47 +02:00
Stefan Hausotte
ad35a6f4f7 docs: add a CHANGELOG.md file
Add a CHANGELOG.md file that uses the "keep a changelog" convention to
make changes to Forji more transparent
2026-06-04 14:44:51 +02:00
Stefan Hausotte
0bcb397805 chore: bump Forji version to 1.5 2026-06-04 14:39:31 +02:00
Stefan Hausotte
330d2bde4f fix: remove redundant notification Dismiss swipe action (#46)
The trailing Dismiss swipe called the same setNotificationRead path as the
leading Mark Read action, since ForgejoKit's NotificationService only exposes
markAsRead (PATCH to-status=read). Dismiss therefore did nothing distinct and
left the row visible under the All/Read filters, just relabeled.

Drop the redundant action in both NotificationsOverviewView and
MergedNotificationsOverviewView. The leading Mark Read swipe (shown for unread
threads, full-swipe by default) remains the single, honest action.

Reviewed-on: https://codeberg.org/secana/Forji/pulls/64
2026-06-04 14:08:20 +02:00
Stefan Hausotte
745b7af45b fix: persist instance removal before deleting keychain on logout (#48)
logout deleted keychain credentials first, then removed the SwiftData instance
with a swallowed `try? modelContext.save()`. If the save failed, the instance
survived with its credentials gone, leaving an account that could not
authenticate. A `nil` default modelContext also let the instance removal be
skipped entirely while credentials were still wiped.

Reorder to delete + save the SwiftData instance first (matching
InstanceListView.deleteInstance) and only delete credentials once that succeeds;
bail out on save failure so the account stays usable. Make modelContext
required to remove the latent orphan path (all call sites already pass one).

Reviewed-on: https://codeberg.org/secana/Forji/pulls/65
2026-06-04 14:08:07 +02:00
Stefan Hausotte
d95ec8ddc5 fix: refresh issue/PR detail after a partial edit failure (#50)
IssueEditView and PullRequestEditView save by running several API calls in
sequence (replaceLabels, editIssue, editPullRequest, requestReviewers,
removeReviewers). Forgejo has no transaction, so if a later call fails the
earlier ones stay committed, yet the catch block only showed an error and never
fired onSaved, leaving the detail view with stale data.

On failure, re-fetch the issue/PR and push the authoritative server state via
onSaved (without dismissing, so the user can retry) before surfacing the error.
True rollback is not possible against the REST API; reflecting the real
committed state is the correct remedy.

Reviewed-on: https://codeberg.org/secana/Forji/pulls/66
2026-06-04 14:07:53 +02:00
secana
8e7f99b6e3 fix: settle merged pagination and drop duplicate rows (#47) 2026-06-04 14:07:32 +02:00
Stefan Hausotte
bec427d7da fix: settle merged pagination and drop duplicate rows (#47)
PaginationState computed hasMore as `fetched.count >= pageSize`, but merged
views return the combined, de-duplicated result of N instances, so the merged
count cleared the single-source threshold and kept hasMore true past the real
end (extra empty fetches). loadMore also appended without de-duping against
already-loaded items, so overlapping involvement queries and shifting pages
could produce duplicate TaggedItem.id entries in the ForEach.

Add an optional dedupeKey to PaginationState that de-duplicates across pages via
a running seen-set, and base hasMore on whether a page contributed new items
when dedupeKey is set. Single-source pagination keeps the page-size heuristic
unchanged. Merged Issues/PRs, Repositories, and Notifications views opt in with
dedupeKey = { $0.id }.
2026-06-04 13:50:40 +02:00
Stefan Hausotte
66fe573cb6 chore: update ForgejoKit dependency to 0.7.0 2026-06-04 13:10:43 +02:00
systemBlue
c2454d3444 docs: add CONTRIBUTING.md (#42) 2026-06-04 13:01:59 +02:00
systemBlue
ed2051ae5d docs: fix typo in README App Store line (#43) 2026-06-04 13:01:41 +02:00
systemBlue
cbd4039e40 feat: add VoiceOver accessibility labels to icon-only buttons (#49) 2026-06-04 13:01:22 +02:00
systemBlue
e9b6be91f5 fix: don't open a duplicate PR when the reviewer request fails (#53) 2026-06-04 13:00:44 +02:00
systemBlue
4f6803cc03 fix: don't crash the background notification poll on an invalid instance URL (#54) 2026-06-04 13:00:25 +02:00
systemBlue
639812348b fix: refresh the merged issue/PR overview after a mutation (#56) 2026-06-04 12:59:59 +02:00
systemBlue
4ef76cf2d7 fix: update the app icon badge in multi-instance mode (#58) 2026-06-04 12:59:43 +02:00
systemBlue
8e56b9d722 feat: make tappable diff lines act as buttons for VoiceOver (#61) 2026-06-04 12:59:17 +02:00
Stefan Hausotte
b0f50eca38 Merge branch 'main' of ssh://codeberg.org/secana/Forji 2026-06-02 18:21:07 +02:00
systemBlue
31d9aeea35 fix: Delete the API token, not just the password, when removing an account (#40)
`deleteInstance` cleared only the keychain password, never the token (`logout` deletes both), so swipe-to-delete left a live API token orphaned; this routes both paths through a new `KeychainManager.deleteCredentials` and adds regression tests (186 ForjiTests green).

I grant Stefan Hausotte an irrevocable, worldwide, royalty-free license to use, sublicense, and distribute my contribution, including through Apple's App Store under the project's App Store exception.

Reviewed-on: https://codeberg.org/secana/Forji/pulls/40
2026-06-02 18:20:43 +02:00
Stefan Hausotte
8648f40c07 fix: guard that prevents edit of accounts to add the same account 2026-06-02 18:20:12 +02:00
systemBlue
89a0cd0bb2 fix: crash merged overviews when two accounts share a sourceKey (#39)
Adding the same server and username twice gives two connections one `sourceKey`, and `rehydrate`'s `Dictionary(uniqueKeysWithValues:)` traps on it, crashing every merged overview on construction; this keeps the first source and adds a regression test (185 ForjiTests green).

I grant Stefan Hausotte an irrevocable, worldwide, royalty-free license to use, sublicense, and distribute my contribution, including through Apple's App Store under the project's App Store exception.

Reviewed-on: https://codeberg.org/secana/Forji/pulls/39
2026-06-02 18:05:53 +02:00
systemBlue
c6dda2cd70 fix: read messages not marked read
Fixes #32. Opening a notification now marks its thread read (via the detail's `.onAppear`) so the tab and icon badges clear; swipe actions unchanged. Adds a UI regression test covering open-to-clear.

I grant Stefan Hausotte an irrevocable, worldwide, royalty-free license to use, sublicense, and distribute my contribution, including through Apple's App Store under the project's App Store exception.
2026-06-02 18:01:07 +02:00
Stefan Hausotte
992c628abd refactor: fix linting issues 2026-05-17 21:40:51 +02:00
Stefan Hausotte
6faf3ad986 chore: update ForgejoKit to 0.6.1 2026-05-17 21:40:36 +02:00
pdurlej
a93130ee09 refactor: use ForgejoKit error categories (#31)
Problem
- SessionRestoreError now duplicates HTTP status-code classification that ForgejoKit exposes in 0.6.0.

Change
- Uses ForgejoKit's httpErrorCategory/httpStatusCode for auth and service restore failures.
- Keeps the same user-facing SessionRestoreError outcomes.
- Leaves network and certificate handling unchanged.

Tests
- DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -quiet -project Forji/Forji.xcodeproj -scheme Forji -destination "platform=iOS Simulator,name=iPhone 17 Pro,OS=26.4.1" build-for-testing -only-testing:ForjiTests/SessionRestoreErrorTests
- DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -quiet -project Forji/Forji.xcodeproj -scheme Forji -destination "platform=iOS Simulator,name=iPhone 17 Pro,OS=26.4.1" test-without-building -only-testing:ForjiTests/SessionRestoreErrorTests

Co-authored-by: Piotr Durlej <pdurlej@users.noreply.github.com>
Reviewed-on: https://codeberg.org/secana/Forji/pulls/31
2026-05-17 21:29:12 +02:00
Stefan Hausotte
075669d87e test: fix warning in test 2026-05-12 18:20:09 +02:00
Stefan Hausotte
fd85a211f6 chore: update ForgejoKit to 0.6.0 2026-05-12 18:19:58 +02:00
pdurlej
5e22431d11 fix: preserve token restore error context (#30)
Problem
- Token-only session restore currently reports every token validation failure as an expired/revoked token, including permission, server, network, and invalid-response failures.

Change
- Preserves token validation error context for token-only instances.
- Maps auth/service/network failures to more specific user-facing restore errors.
- Leaves password fallback behavior unchanged for password-based instances.

Tests
- DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -quiet -project Forji/Forji.xcodeproj -scheme Forji -destination "platform=iOS Simulator,name=iPhone 17 Pro,OS=26.4.1" build-for-testing -only-testing:ForjiTests/SessionRestoreErrorTests
- DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -quiet -project Forji/Forji.xcodeproj -scheme Forji -destination "platform=iOS Simulator,name=iPhone 17 Pro,OS=26.4.1" test-without-building -only-testing:ForjiTests/SessionRestoreErrorTests

Co-authored-by: Piotr Durlej <pdurlej@users.noreply.github.com>
Reviewed-on: https://codeberg.org/secana/Forji/pulls/30
Reviewed-by: secana <secana@noreply.codeberg.org>
2026-05-12 17:45:02 +02:00
Stefan Hausotte
db4e54db6b chore: add "just sim-update" command
Updates the iOS simulators to the latest version in XCode
2026-05-10 11:01:40 +02:00
pdurlej
0b815335a9 fix: key multi-instance fallback by account (#29)
## Summary

This changes the multi-instance fallback map to key bootstrapped connections by account instead of server URL only.

Previously, `connect(instances:)` built a dictionary keyed only by `instance.serverURL`. If two accounts used the same Forgejo instance, `Dictionary(uniqueKeysWithValues:)` could trap on duplicate keys, and fallback lookup could also reuse the wrong account for a failed restore.

## Changes

- Add an account key based on normalized server URL + username.
- Use that key for bootstrapped connection fallback in `MultiInstanceManager`.
- Add a regression test covering two token-auth accounts on the same server URL.

## Verification

- `git diff --check` passes.
- I could not run `xcodebuild` locally because the available Xcode install has not accepted the license on this machine (`xcodebuild` exits before build/test execution).

Co-authored-by: Piotr Durlej <pdurlej@users.noreply.github.com>
Reviewed-on: https://codeberg.org/secana/Forji/pulls/29
Reviewed-by: secana <secana@noreply.codeberg.org>
2026-05-10 10:39:02 +02:00
Stefan Hausotte
f7eba701e2 chore: update Forji version 2026-05-07 19:28:42 +02:00
Stefan Hausotte
57bde0934f refactor: small name changes 2026-05-07 19:17:49 +02:00
Stefan Hausotte
2e07f6da4d test: make tests more stable 2026-05-07 19:07:40 +02:00
Stefan Hausotte
ea9c9825e0 test: fix race condition in tests 2026-05-07 18:21:09 +02:00
Voislav Vasiljevski
3d3de81f2b [Draft] feat: Actions tab — depends on ForgejoKit#1 (#28)
Closes #3.

Adds an "Actions" tab to the repository view, surfacing Forgejo Actions runs and (experimentally) their jobs, steps, and logs.

## What's in this branch

1. ~~**`chore: point ForgejoKit at local checkout for Actions PR`** — temporary `XCLocalSwiftPackageReference` so the feature commit compiles against the unreleased ForgejoKit changes. Drop this commit before merge and replace with `chore: bump ForgejoKit to <new version>` once the ForgejoKit PR ships in a release.~~

   **`chore: update ForgejoKit to released version 0.4.0`** + **`chore: add ForgejoKit 0.4.0 to Package.resolved`** — switches from local path override to a remote pin at `secana/ForgejoKit` `0.4.0`.

2. **`feat: add Actions tab to repository view`** — the actual feature.

## UI scope

- New `Actions` tab, shown only when `repository.hasActions == true`.
- Runs list with All / Running / Success / Failed filter, pagination, pull-to-refresh, empty state.
- Run detail with header, metadata (workflow file, event, trigger user, started/duration — zero-Date suppressed), and an experimental Jobs section.
- Job view with collapsible step rows; logs lazy-load on expand via `logCursors`.
- "Open in browser" toolbar item using each run's `html_url`.

## Experimental jobs/steps view

Forgejo's `/api/v1` doesn't expose jobs, steps, or step logs for a run. This PR opts into ForgejoKit's experimental `fetchRunView` (backed by Forgejo's web-UI route) so we can render a GitHub-Actions-style view today. The Jobs section and Steps screen are explicitly labelled "Experimental" in the UI, and a footer notes the output may change between Forgejo versions. Happy to drop this section if you'd rather ship public-API only first.

## Defensive handling

- Forgejo serialises an unset `time.Time` as `0001-01-01T00:00:00Z` (showed up as "Started 56 yrs, 4 mths" / "Duration 493 906h" on a cancelled run). Sanitised display helpers suppress any pre-2000 date.
- The experimental `fetchRunView` resolves Forgejo's `RedirectToLatestAttempt` server-side before POSTing, so attempts with non-zero attempt numbers work. (Without this, Forgejo returns 500 "task with job_id N and attempt 0: resource does not exist".)

## Tests

- Unit: `WorkflowRunFilterTests`, `WorkflowStatusIconTests` (filter mapping, status enum compatibility against Forgejo's documented values, status icon mapping, run helpers, zero-date guard).
- UI: `ActionsUITests` (read-only smoke test).

Co-authored-by: Voislav Vasiljevski <voislav@voioo.cz>
Reviewed-on: https://codeberg.org/secana/Forji/pulls/28
2026-05-07 18:06:17 +02:00
Stefan Hausotte
d7a883efda test: update forgejo to version 15 2026-04-20 16:52:59 +02:00
Stefan Hausotte
d879440577 fix: "dismiss" not available on notifactions in "All Instance" view 2026-04-14 18:46:34 +02:00
Stefan Hausotte
666b8e57c3 chore: update version 2026-04-01 13:49:54 +02:00
Stefan Hausotte
bf65e6d13d fix: multi-instance persistent cache 2026-03-31 23:09:18 +02:00
Stefan Hausotte
2c2b61e249 feat: filter for repos #16 2026-03-31 20:11:22 +02:00
secana
095adfb22a Merge pull request 'feat/persistent-cache' (#25) from feat/persistent-cache into main
Reviewed-on: https://codeberg.org/secana/Forji/pulls/25
2026-03-28 19:32:18 +01:00
Stefan Hausotte
25de1be2d9 feat: disable PR view for mirrored repos 2026-03-28 19:31:07 +01:00
Stefan Hausotte
5aa65525cf feat: persistent cache #17 2026-03-28 19:31:07 +01:00
Stefan Hausotte
c42ed9552e test: improve test runtime
Reviewed-on: https://codeberg.org/secana/Forji/pulls/24
Co-authored-by: Stefan Hausotte <stefan.hausotte@gmx.de>
Co-committed-by: Stefan Hausotte <stefan.hausotte@gmx.de>
2026-03-23 19:07:55 +01:00
Stefan Hausotte
253f3e88d1 feat: implement iOS notifications #6
Naive implementation for iOS notifications.

Problem: Forgejo does not support push notifications. We need to pull every  X minutes for new notifications. The even bigger problem: iOS does not support background polling. So this is more a "as good as possible" but not good approach.

Reviewed-on: https://codeberg.org/secana/Forji/pulls/23
Co-authored-by: Stefan Hausotte <stefan.hausotte@gmx.de>
Co-committed-by: Stefan Hausotte <stefan.hausotte@gmx.de>
2026-03-22 17:43:21 +01:00