Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file

version: 2
updates:
- package-ecosystem: "swift" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
34 changes: 34 additions & 0 deletions .github/workflows/reader.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Reader OS 27
on:
pull_request:
paths: ['ReaderKit/**', 'ReaderApp/**', 'Reader.xcodeproj/**', 'scripts/test-reader.sh', '.github/workflows/reader.yml']
push:
branches: [main, 'codex/intelligence-reader']
paths: ['ReaderKit/**', 'ReaderApp/**', 'Reader.xcodeproj/**', 'scripts/test-reader.sh', '.github/workflows/reader.yml']
permissions:
contents: read
concurrency:
group: reader-${{ github.ref }}
cancel-in-progress: true
jobs:
tests:
# https://github.com/actions/runner-images/blob/main/images/macos/xcode-27-arm64-Readme.md
runs-on: xcode-27
strategy:
fail-fast: false
matrix:
platform: [macos, ios, ipad]
env:
DEVELOPER_DIR: /Applications/Xcode_27.1.app/Contents/Developer
steps:
- uses: actions/checkout@v5
- name: Verify aggregate failure propagation
run: python3 -m unittest discover -s scripts/tests -v
- name: Test Reader
run: READER_RESULTS_DIR="$RUNNER_TEMP/reader-results" bash scripts/test-reader.sh '${{ matrix.platform }}'
- uses: actions/upload-artifact@v4
if: always()
with:
name: reader-${{ matrix.platform }}-results
path: ${{ runner.temp }}/reader-results/*.xcresult
if-no-files-found: warn
2 changes: 1 addition & 1 deletion NetNewsWire.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1469,7 +1469,7 @@
repositoryURL = "https://github.com/sparkle-project/Sparkle.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.9.5;
minimumVersion = 2.10.0;
};
};
179D280926F6F93D003B2E0A /* XCRemoteSwiftPackageReference "Zip" */ = {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## Reader-Fork ab OS 27

Die neue gemeinsame SwiftUI-/SwiftData-App mit FreshRSS und lokalen KI-Tagesübersichten liegt in **`Reader.xcodeproj`**. Einrichtung, Testbefehle und aktuelle Einschränkungen beschreibt die [Reader-Anleitung](ReaderKit/README.md). Die [Abnahmematrix](docs/reader/acceptance.md) trennt umgesetzte Funktionen von ausstehenden Geräte- und Dienstnachweisen. [GitHub-Arbeitsplan: #45](https://github.com/mpwg/NetNewsWire/issues/45).

Die nachfolgende Anleitung beschreibt die ursprünglichen NetNewsWire-Targets, die während des Umbaus erhalten bleiben.

<img src=Technotes/Images/icon_1024.png height=128 width=128 style="display: block; margin: auto;">

# NetNewsWire
Expand Down
2 changes: 2 additions & 0 deletions Reader.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions Reader.xcodeproj/xcshareddata/xcschemes/Reader.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme LastUpgradeVersion="2710" version="1.7">
<BuildAction parallelizeBuildables="YES" buildImplicitDependencies="YES"><BuildActionEntries>
<BuildActionEntry buildForTesting="YES" buildForRunning="YES" buildForProfiling="YES" buildForArchiving="YES" buildForAnalyzing="YES">
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="30F22C3AE6F3FD3B17E57F9D" BuildableName="Reader.app" BlueprintName="Reader" ReferencedContainer="container:Reader.xcodeproj"/>
</BuildActionEntry>
</BuildActionEntries></BuildAction>
<TestAction buildConfiguration="Debug" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.IDEFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv="YES">
<TestPlans><TestPlanReference reference="container:ReaderApp/Reader.xctestplan" default="YES"/></TestPlans>
</TestAction>
<LaunchAction buildConfiguration="Debug" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.IDEFoundation.Launcher.LLDB" launchStyle="0" useCustomWorkingDirectory="NO" ignoresPersistentStateOnLaunch="NO" debugDocumentVersioning="YES" debugServiceExtension="internal" allowLocationSimulation="YES">
<BuildableProductRunnable runnableDebuggingMode="0"><BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="30F22C3AE6F3FD3B17E57F9D" BuildableName="Reader.app" BlueprintName="Reader" ReferencedContainer="container:Reader.xcodeproj"/></BuildableProductRunnable>
</LaunchAction>
<ProfileAction buildConfiguration="Release" shouldUseLaunchSchemeArgsEnv="YES" savedToolIdentifier="" useCustomWorkingDirectory="NO" debugDocumentVersioning="YES"/>
<AnalyzeAction buildConfiguration="Debug"/>
<ArchiveAction buildConfiguration="Release" revealArchiveInOrganizer="YES"/>
</Scheme>
22 changes: 22 additions & 0 deletions ReaderApp/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>CFBundleDisplayName</key><string>Reader</string>
<key>CFBundleDevelopmentRegion</key><string>de</string>
<key>CFBundleExecutable</key><string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key><string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleName</key><string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleShortVersionString</key><string>0.1.0</string>
<key>CFBundleVersion</key><string>1</string>
<key>LSApplicationCategoryType</key><string>public.app-category.news</string>
<key>NSPrincipalClass</key><string>NSApplication</string>
<key>UIApplicationSceneManifest</key><dict><key>UIApplicationSupportsMultipleScenes</key><true/></dict>
<key>UILaunchScreen</key><dict/>
<key>UISupportedInterfaceOrientations</key><array>
<string>UIInterfaceOrientationPortrait</string><string>UIInterfaceOrientationLandscapeLeft</string><string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key><array>
<string>UIInterfaceOrientationPortrait</string><string>UIInterfaceOrientationPortraitUpsideDown</string><string>UIInterfaceOrientationLandscapeLeft</string><string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict></plist>
21 changes: 21 additions & 0 deletions ReaderApp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Reader-App

Eigenständige SwiftUI-App für macOS, iOS und iPadOS ab 27. Das alte NetNewsWire-Projekt bleibt während der Abnahme bestehen. Der neue App-Target bindet ausschließlich `ReaderKit` ein und übernimmt weder die alten AppKit-/UIKit-Controller noch Objective-C-/FMDB-, CloudKit-, Widget- oder Erweiterungstargets.

## Bauen

`python3 ReaderApp/generate_project.py` erzeugt `Reader.xcodeproj` und den gemeinsamen Testplan aus den aktuellen Pakettests. Anschließend das Projekt und die Scheme `Reader` öffnen. Debug verwendet `eu.mpwg.reader.debug`, Release `eu.mpwg.reader`. Jeder Build speichert unter seinem eigenen Application-Support-Verzeichnis; es gibt keine Migration bestehender NetNewsWire-Datenbanken.

Für lokale Mac-UI-Tests muss Xcode den Test-Runner einschließlich seiner eingebetteten Testbundles gültig signieren. Lokal ist eine Ad-hoc-Signatur mit `CODE_SIGNING_ALLOWED=YES CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=YES DEVELOPMENT_TEAM= CODE_SIGN_STYLE=Manual` möglich. `CODE_SIGNING_ALLOWED=NO` genügt für einen Mac-Build, erzeugt aber keinen ausführbaren, gültig signierten UI-Test-Runner. Vor `test-without-building` die Signatur mit `codesign --verify --deep --strict` prüfen.

`ReaderSmokeTests` startet mit `--ui-testing` und einem isolierten In-Memory-Store; keine persönlichen Abonnements oder Zugangsdaten sind dafür nötig. Der gemeinsame Testplan enthält Fachtests mit Swift Testing und UI-Automation mit XCTest. Der UIKit-Modernisierungsschnitt besteht im neuen SwiftUI-`App`-/`WindowGroup`-Lebenszyklus; es gibt keinen globalen Zugriff auf die aktuelle Szene, den Hauptbildschirm oder AppDelegate-Zustand.

## Plattformverträge

- Jede Szene besitzt Auswahl, Suche, Sheets und Abfragegeneration selbst; Repository und kontoabhängige Dienste sind gemeinsam.
- Eine Szene darf die laufende Arbeit einer anderen Szene nicht suspendieren. Externe Links werden über SwiftUI `Link` geöffnet.
- Layout folgt dem verfügbaren Fenster und den systemseitigen Safe Areas. Keine geräteweite Portrait-/Landscape- oder Displaygrößenannahme.
- Artikel verwenden `WebView`/`WebPage`; Inhalts-JavaScript ist deaktiviert. Die generierte HTML-Ansicht hat eine begrenzte Content Security Policy.
- PCC ist eine optionale Runtime-Capability. Eine produktive Freigabe, Signierung oder App-Store-Abnahme ist mit dieser Projektgrundlage noch nicht belegt.

Physische Geräte, VoiceOver, iPad-Mehrfenster, alle Tastaturformen und Langzeitlast müssen separat praktisch abgenommen werden. Ein Build oder ein Session-Test ersetzt diese Nachweise nicht.
7 changes: 7 additions & 0 deletions ReaderApp/Reader.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>com.apple.security.app-sandbox</key><true/>
<key>com.apple.security.network.client</key><true/>
<key>com.apple.security.files.user-selected.read-write</key><true/>
</dict></plist>
33 changes: 33 additions & 0 deletions ReaderApp/Reader.xctestplan
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"configurations": [
{
"id": "E373F31C-94B3-4AE4-9923-756B8F8359E3",
"name": "Reader",
"options": {}
}
],
"defaultOptions": {
"targetForVariableExpansion": {
"containerPath": "container:Reader.xcodeproj",
"identifier": "30F22C3AE6F3FD3B17E57F9D",
"name": "Reader"
}
},
"testTargets": [
{
"target": {
"containerPath": "container:Reader.xcodeproj",
"identifier": "D58A296CCBFB0C65D1E4740C",
"name": "ReaderContractTests"
}
},
{
"target": {
"containerPath": "container:Reader.xcodeproj",
"identifier": "50CA53E53B1698EDC5D0EE27",
"name": "ReaderSmokeTests"
}
}
],
"version": 1
}
53 changes: 53 additions & 0 deletions ReaderApp/ReaderApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import SwiftUI
import ReaderDomain
import ReaderPersistence
import ReaderSync
import ReaderContent
import ReaderIntelligence
import ReaderUI

@main
struct ReaderApplication: App {
private let bootstrap: ReaderBootstrap

init() { bootstrap = ReaderBootstrap() }

var body: some Scene {
WindowGroup(id: "reader") {
if let services = bootstrap.services {
ReaderWindow(services: services)
} else {
ContentUnavailableView("Reader konnte nicht starten", systemImage: "externaldrive.badge.exclamationmark",
description: Text(bootstrap.errorMessage ?? "Der lokale Datenspeicher ist nicht verfügbar."))
.padding()
}
}
.defaultSize(width: 1200, height: 800)
}
}

@MainActor
private final class ReaderBootstrap {
let services: ReaderServices?
let errorMessage: String?

init() {
do {
let bundleID = Bundle.main.bundleIdentifier ?? "eu.mpwg.reader.debug"
let directory = try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask,
appropriateFor: nil, create: true).appendingPathComponent(bundleID, isDirectory: true)
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
let uiTesting = ProcessInfo.processInfo.arguments.contains("--ui-testing")
let store = try ReaderStore(url: uiTesting ? nil : directory.appendingPathComponent("Reader.store"), inMemory: uiTesting)
let credentials = KeychainCredentialStore(service: bundleID)
let client = FreshRSSClient(repository: store, credentials: credentials)
let content = ArticleContentService()
let digests = DigestCoordinator(repository: store, generator: AppleSummaryGenerator(), extractor: content)
services = ReaderServices(repository: store, credentials: credentials, client: client, digests: digests, content: content)
errorMessage = nil
} catch {
services = nil
errorMessage = error.localizedDescription
}
}
}
75 changes: 75 additions & 0 deletions ReaderApp/Tests/SmokeTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import XCTest
#if os(macOS)
import AppKit
import ApplicationServices
#endif

@MainActor
final class ReaderSmokeTests: XCTestCase {
func testConnectionFormIsReachableWithoutNetwork() throws {
let app = XCUIApplication()
app.launchArguments = ["--ui-testing", "-ApplePersistenceIgnoreState", "YES"]
app.launch()
recordAccessibilityDiagnosis()
let connect = app.buttons["connectFreshRSS"]
XCTAssertTrue(connect.waitForExistence(timeout: 15))
connect.clickOrTap()
XCTAssertTrue(app.textFields["serverURL"].waitForExistence(timeout: 5))
XCTAssertTrue(app.textFields["username"].exists)
XCTAssertTrue(app.secureTextFields["apiPassword"].exists)
XCTAssertFalse(app.buttons["submitConnection"].isEnabled)
app.buttons["Abbrechen"].firstMatch.clickOrTap()
XCTAssertTrue(connect.waitForExistence(timeout: 5))
}

#if os(macOS)
func testNewWindowHasIndependentConnectionForm() throws {
let app = XCUIApplication()
app.launchArguments = ["--ui-testing", "-ApplePersistenceIgnoreState", "YES"]
app.launch()
recordAccessibilityDiagnosis()
XCTAssertTrue(app.buttons["connectFreshRSS"].waitForExistence(timeout: 15))
app.typeKey("n", modifierFlags: .command)
XCTAssertTrue(app.windows.element(boundBy: 1).waitForExistence(timeout: 5))
let first = app.windows.element(boundBy: 0)
let second = app.windows.element(boundBy: 1)
second.buttons["connectFreshRSS"].click()
let username = app.textFields["username"]
XCTAssertTrue(username.waitForExistence(timeout: 5))
username.click()
username.typeText("Fenster B")
app.buttons["Abbrechen"].firstMatch.click()
first.buttons["connectFreshRSS"].click()
XCTAssertTrue(app.textFields["username"].waitForExistence(timeout: 5))
XCTAssertEqual(app.textFields["username"].value as? String, "")
app.buttons["Abbrechen"].firstMatch.click()
XCTAssertTrue(second.buttons["connectFreshRSS"].exists)
}
#endif

private func recordAccessibilityDiagnosis() {
#if os(macOS)
let entries = NSRunningApplication.runningApplications(withBundleIdentifier: "eu.mpwg.reader.debug").map { application in
let element = AXUIElementCreateApplication(application.processIdentifier)
var windows: CFTypeRef?
let result = AXUIElementCopyAttributeValue(element, kAXWindowsAttribute as CFString, &windows)
return "pid=\(application.processIdentifier), active=\(application.isActive), AXWindowsStatus=\(result.rawValue), windowCount=\((windows as? [AXUIElement])?.count ?? -1)"
}
let diagnosis = "AXIsProcessTrusted=\(AXIsProcessTrusted()); " + entries.joined(separator: "; ")
let attachment = XCTAttachment(string: diagnosis)
attachment.name = "Accessibility-Diagnose"
attachment.lifetime = .keepAlways
add(attachment)
#endif
}
}

private extension XCUIElement {
func clickOrTap() {
#if os(macOS)
click()
#else
tap()
#endif
}
}
Loading