Skip to content
Merged
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
8 changes: 5 additions & 3 deletions .opencode/skills/xcode-verify/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ for that requested operation.
## Setup

1. Before the first XcodeBuildMCP build or test call, use `session_show_defaults`.
2. Before building, create `Secrets.xcconfig` from the template in `AGENTS.md` if it is missing; never overwrite it.
2. Before building, create `Secrets.xcconfig` from the setup template in `README.md` if it is missing; never overwrite it.
3. Prefer XcodeBuildMCP and use the project, scheme, and simulator defaults from `.xcodebuildmcp/config.yaml`. Only repair
defaults that are missing or wrong.
4. Disable code signing for builds and tests. Add the test timeout arguments documented in `AGENTS.md` for test runs.
5. If an MCP request fails or times out, use the complete `xcodebuild` fallback from `AGENTS.md`.
4. Disable code signing for builds and tests with `CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO`. For tests, add
`-test-timeouts-enabled YES -maximum-test-execution-time-allowance 120`.
5. If an MCP request fails or times out, use `xcodebuild` with the current project, scheme, and destination from the
session defaults or `.xcodebuildmcp/config.yaml`; preserve the same signing and timeout arguments.

## Operations

Expand Down
265 changes: 77 additions & 188 deletions AGENTS.md

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ openclient-llm/ # iOS target
│ │ ├── Chat/
│ │ │ ├── Models/
│ │ │ ├── Repositories/
│ │ │ ├── Tools/
│ │ │ ├── UseCases/
│ │ │ ├── ViewModels/
│ │ │ └── Views/
Expand Down Expand Up @@ -57,6 +58,7 @@ openclient-llm/ # iOS target
│ │ │ └── Views/
│ │ ├── Settings/
│ │ │ ├── Models/
│ │ │ ├── UseCases/
│ │ │ ├── ViewModels/
│ │ │ └── Views/
│ │ ├── Shortcuts/ # AppIntents and AppShortcutsProvider
Expand All @@ -72,6 +74,7 @@ openclient-llm/ # iOS target
│ │ ├── Models/ # App-side share payload and other core models
│ │ ├── Networking/
│ │ │ └── Models/
│ │ ├── Tips/
│ │ ├── Utils/
│ │ └── Views/
│ └── Resources/
Expand Down Expand Up @@ -130,7 +133,8 @@ openclient-llm-test/ # Unit tests
│ ├── Onboarding/
│ ├── PromptTemplates/
│ └── Settings/
└── Mocks/ # MockXxx per protocol, @unchecked Sendable
├── Mocks/ # Reusable protocol-backed test doubles
└── Support/ # Shared test harnesses and infrastructure
```

The Xcode project contains six native targets: `openclient-llm`, `openclient-llm-macOS`,
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

## [1.7.10-build-115] - 2026-09-19

### Changed

- Build compatibility with Xcode 27 and the iOS 27 and macOS 27 SDKs
- Tag filter chips in the conversation list now render with Liquid Glass on macOS, matching iOS and iPadOS
- Onboarding actions now use Liquid Glass button styles on macOS, matching iOS and iPadOS
- The remote banner now animates into view on macOS with the same transition used on iOS and iPadOS

### Fixed

- The conversation list no longer shows a duplicate separator between the tag filter bar and the conversation sections
- The Models screen no longer repeatedly requests model data when its SwiftUI task restarts
- **Minor bug fixes**

## [1.7.5-build-110] - 2026-09-15

### Added
Expand Down
8 changes: 8 additions & 0 deletions IMPROVEMENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Improvements

This file tracks optional follow-up work that is not part of the current project contract. Specifications must describe
supported behavior and invariants, not pending improvements.

## Networking

- Redact or remove the short undecodable SSE payload preview currently emitted by `ChatRepository` debug logging.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<img src="https://img.shields.io/badge/License-AGPL%20v3.0-blue?style=flat-square" alt="License" />
<img src="https://img.shields.io/badge/Platform-iOS%2026+%20|%20iPadOS%2026+%20|%20macOS%2026+-blue?style=flat-square" alt="Platform" />
<img src="https://img.shields.io/badge/Swift-6+-orange?style=flat-square&logo=swift" alt="Swift" />
<img src="https://img.shields.io/badge/Version-1.7.5-brightgreen?style=flat-square" alt="Version 1.7.5" />
<img src="https://img.shields.io/badge/Version-1.7.10-brightgreen?style=flat-square" alt="Version 1.7.10" />
</p>

OpenClient connects directly to the AI server you configure, without an OpenClient-hosted proxy or subscription.
Expand Down Expand Up @@ -116,8 +116,6 @@ servers that provide the endpoints used by your selected features; point the app
| WidgetKit | Native iOS, iPadOS, and macOS widgets plus a New Chat system control |
| Votice | In-app feedback & feature requests |

This project was developed entirely with Xcode, Visual Studio Code and GitHub Copilot (with Claude Opus / Sonnet 4.6).

## Architecture

The project follows **MVVM + UseCase + Repository + Manager** with Swift strict concurrency and `async/await`. Code is organized by feature under `Shared/`, shared across iOS and macOS targets. Platform-specific UI lives in each target's own folder.
Expand Down
4 changes: 2 additions & 2 deletions ShareExtension/App/ShareViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ final class ShareViewController: SLComposeServiceViewController {
try? ShareExtensionStore.save(item)
context?.completeRequest(returningItems: []) { _ in
guard let appURL = URL(string: "openclient://share") else { return }
Task { @MainActor [weak self] in
self?.openContainingApp(url: appURL)
Task { @MainActor in
self.openContainingApp(url: appURL)
}
}
}
Expand Down
16 changes: 6 additions & 10 deletions TestFlight/WhatToTest.en-US.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
Hi there! We've got some great new features for you in this update.

• Your favorite chat model just got some talented teammates! Models that support tools can now call on specialists to analyze photos or create images, without switching models or starting another chat. Ask about a picture, dream up something new, or revisit an image from earlier in the conversation. It all happens in the same chat, and you can see which specialist is helping along the way.
• You're in control of the team. Choose your vision and image generation specialists in the new Image and Vision section in Models. Use one compatible model for both jobs, mix and match, or choose None to turn off either kind of help. If your chat model already understands or creates images, it keeps doing that job itself.
• Your assistant's toolbox is now in your hands! Open the new Tools section in Settings to see what each built-in tool does and turn it on or off. Everything is listed alphabetically in your language, and OpenClient remembers your choices.
• Got a picture you'd like to change? Image-generation models with vision now accept one or more reference images for editing on iPhone, iPad, and Mac. Trying again keeps your original prompt and references, so you don't have to set everything up from scratch.
• We've given your creations some extra care, too. Images stay in the conversation if you stop a reply after they arrive, and regenerating just the text keeps the image without requesting another generation. We've improved image-only replies, image format handling, and references in long chats and imported backups. If an analysis request is too large for your specialist, clearer guidance helps you adjust it. Private chats still keep images only for the session.
• Your conversations just got easier to read. We've brought your messages and the model's replies closer together, with each reply's time, details, and actions in a compact row. Less empty space, more conversation in view.
• Better formatting for your replies! Markdown tables now display correctly even without separators at their outer edges. Footnotes appear as numbered notes at the end of the message, and escaped characters are handled correctly inside tables, too.
• Small details make a difference, too. You can now see subscripts and superscripts, like H₂O or x², even inside links and alongside bold or italic text. Their size follows your text size on iPhone, iPad, and Mac.
• Finding a chat on iPad is now more convenient. Search directly from the top of the sidebar and see matching conversations on the right as you type. Open a result to jump into that chat and dismiss the keyboard; your search stays there for later. Prefer the top bar? The search button is still available there, too.
• Voice setup is a little easier as well. If you haven't selected a model, or your previous choice is no longer available, text-to-speech picks the first available model and speech-to-text selects Apple speech recognition. Both work even when there's only one option. We've also included smaller fixes and improvements to keep things running smoothly.
• OpenClient is now ready for Xcode 27 and the latest iOS 27 and macOS 27 SDKs, keeping your experience up to date across iPhone, iPad, and Mac.
• On Mac, tag filters in your conversation list and the onboarding buttons now use the same Liquid Glass style you already enjoy on iPhone and iPad.
• Announcements on the Home screen now animate into view on Mac with the same smooth transition used on iPhone and iPad.
• Your conversation list no longer shows a duplicate separator above your chats.
• The Models screen no longer repeats requests while loading, so it stays responsive.
• Minor bug fixes and improvements for a smoother experience.

Thanks for your continued support and for helping us build the best possible LLM client together.

Expand Down
16 changes: 6 additions & 10 deletions TestFlight/WhatToTest.es-ES.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
¡Hola! Esta actualización viene cargada de novedades.

• ¡Tu modelo de chat favorito ahora tiene nuevos compañeros de equipo! Los modelos compatibles con herramientas pueden recurrir a especialistas para analizar fotos o crear imágenes, sin cambiar de modelo ni iniciar otro chat. Pregunta por una imagen, crea algo nuevo o vuelve a una imagen anterior de la conversación. Todo ocurre en el mismo chat y podrás ver qué especialista está ayudando en cada momento.
• Tú decides quién forma parte del equipo. Elige tus especialistas de visión y generación de imágenes en la nueva sección Imagen y visión de Modelos. Puedes usar un mismo modelo compatible para ambas tareas, combinar modelos distintos o seleccionar Ninguno para desactivar cualquiera de estas funciones. Si tu modelo de chat ya puede interpretar o crear imágenes, seguirá haciéndolo por sí mismo.
• ¡Ahora tú controlas las herramientas de tu asistente! Abre la nueva sección Herramientas de Ajustes para ver qué hace cada herramienta integrada y activarla o desactivarla. Todas aparecen ordenadas alfabéticamente en tu idioma y OpenClient recuerda tus preferencias.
• ¿Quieres modificar una imagen? Los modelos de generación de imágenes con capacidad de visión ahora pueden utilizar una o varias imágenes de referencia para editarlas en iPhone, iPad y Mac. Si vuelves a intentarlo, se conservan tanto el prompt original como las imágenes de referencia, para que no tengas que configurarlo todo de nuevo.
• También hemos mejorado el tratamiento de las imágenes generadas. Las imágenes permanecen en la conversación si detienes una respuesta después de que se hayan generado, y regenerar solo el texto conserva la imagen sin solicitar una nueva generación. También hemos mejorado las respuestas que contienen únicamente imágenes, la gestión de formatos y las referencias en conversaciones largas y copias de seguridad importadas. Si una solicitud de análisis es demasiado grande para tu especialista, recibirás indicaciones más claras para poder ajustarla. Los chats privados siguen conservando las imágenes únicamente durante la sesión.
• Tus conversaciones, más cómodas de leer. Hemos acercado tus mensajes y las respuestas del modelo, y reunido la hora, los detalles y las acciones de cada respuesta en una fila compacta. Menos espacio vacío y más conversación a la vista.
• ¡Mejor formato para las respuestas! Las tablas de Markdown ahora se muestran bien aunque no incluyan separadores en los extremos. Las notas al pie aparecen numeradas al final del mensaje y los caracteres escapados se respetan también dentro de las tablas.
• Los pequeños detalles también cuentan. Ya puedes ver subíndices y superíndices, como H₂O o x², incluso en enlaces y junto a negrita o cursiva. Su tamaño se adapta al del texto en iPhone, iPad y Mac.
• Encontrar un chat en iPad es ahora más cómodo. Busca directamente desde la parte superior del menú lateral y verás las conversaciones coincidentes a la derecha mientras escribes. Abre un resultado para entrar en ese chat y cerrar el teclado; la búsqueda se conserva para retomarla después. ¿Prefieres la barra superior? La lupa sigue disponible también ahí.
• La configuración de voz también es ahora más sencilla. Si no has seleccionado ningún modelo o el que usabas ya no está disponible, la conversión de texto a voz seleccionará el primer modelo disponible y la conversión de voz a texto utilizará el reconocimiento de voz de Apple. Ambas funciones también funcionan cuando solo hay una opción disponible. Además, hemos incluido pequeñas correcciones y mejoras para que todo siga funcionando como debe.
• OpenClient ya está preparado para Xcode 27 y los últimos SDK de iOS 27 y macOS 27, para ofrecerte una experiencia actualizada en iPhone, iPad y Mac.
• En Mac, los filtros de etiquetas de la lista de conversaciones y los botones del onboarding ahora usan el mismo estilo Liquid Glass que ya disfrutas en iPhone y iPad.
• Los anuncios de la pantalla de Inicio ahora aparecen con una animación fluida en Mac, con la misma transición que en iPhone e iPad.
• La lista de conversaciones ya no muestra un separador duplicado encima de tus chats.
• La pantalla de Modelos ya no repite solicitudes mientras se carga, para mantenerse ágil.
• Pequeñas correcciones de errores y mejoras para disfrutar de una experiencia más fluida.

Gracias por seguir apoyándonos y por ayudarnos a crear juntos el mejor cliente posible para modelos de lenguaje.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<array>
<string>iCloud.com.artcc.openclient-llm</string>
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.artcc.openclient-llm</string>
Expand Down
14 changes: 6 additions & 8 deletions openclient-llm-macOS/Views/AppCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,23 @@ import SwiftUI
struct AppCommands: Commands {
// MARK: - Properties

@FocusedValue(\.newChatAction) private var newChatAction
@FocusedValue(\.newPrivateChatAction) private var newPrivateChatAction
@FocusedValue(\.conversationListViewModel) private var conversationListViewModel

// MARK: - View

var body: some Commands {
CommandGroup(replacing: .newItem) {
Button(String(localized: "New Chat")) {
newChatAction?()
conversationListViewModel?.send(.newConversationTapped)
}
.keyboardShortcut("n", modifiers: .command)
.disabled(newChatAction == nil)
.disabled(conversationListViewModel == nil)

Button(String(localized: "New Private Chat")) {
newPrivateChatAction?()
conversationListViewModel?.send(.newPrivateConversationTapped)
}
.keyboardShortcut("n", modifiers: [.command, .shift])
.disabled(newPrivateChatAction == nil)
.disabled(conversationListViewModel == nil)

Divider()
}
Expand All @@ -38,6 +37,5 @@ struct AppCommands: Commands {
// MARK: - FocusedValues

extension FocusedValues {
@Entry var newChatAction: (() -> Void)?
@Entry var newPrivateChatAction: (() -> Void)?
@Entry var conversationListViewModel: ConversationListViewModel?
}
18 changes: 12 additions & 6 deletions openclient-llm-test/Features/Chat/ChatViewModelTests+TTS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ extension ChatViewModelTests {
saveConversationUseCase: mockSaveConversation,
synthesizeSpeechUseCase: mockSynthesize,
getChatPreferencesUseCase: mockGetChatPreferences,
getConversationStartersUseCase: mockGetConversationStarters
getConversationStartersUseCase: mockGetConversationStarters,
playAudioUseCase: mockPlayAudio
)

sut.send(.viewAppeared)
Expand Down Expand Up @@ -59,7 +60,8 @@ extension ChatViewModelTests {
saveConversationUseCase: mockSaveConversation,
synthesizeSpeechUseCase: mockSynthesize,
getChatPreferencesUseCase: mockGetChatPreferences,
getConversationStartersUseCase: mockGetConversationStarters
getConversationStartersUseCase: mockGetConversationStarters,
playAudioUseCase: mockPlayAudio
)

sut.send(.viewAppeared)
Expand Down Expand Up @@ -93,7 +95,8 @@ extension ChatViewModelTests {
saveConversationUseCase: mockSaveConversation,
synthesizeSpeechUseCase: mockSynthesize,
getChatPreferencesUseCase: mockGetChatPreferences,
getConversationStartersUseCase: mockGetConversationStarters
getConversationStartersUseCase: mockGetConversationStarters,
playAudioUseCase: mockPlayAudio
)

sut.send(.viewAppeared)
Expand Down Expand Up @@ -124,7 +127,8 @@ extension ChatViewModelTests {
saveConversationUseCase: mockSaveConversation,
synthesizeSpeechUseCase: mockSynthesize,
getChatPreferencesUseCase: mockGetChatPreferences,
getConversationStartersUseCase: mockGetConversationStarters
getConversationStartersUseCase: mockGetConversationStarters,
playAudioUseCase: mockPlayAudio
)

sut.send(.viewAppeared)
Expand Down Expand Up @@ -161,7 +165,8 @@ extension ChatViewModelTests {
saveConversationUseCase: mockSaveConversation,
synthesizeSpeechUseCase: mockSynthesize,
getChatPreferencesUseCase: mockGetChatPreferences,
getConversationStartersUseCase: mockGetConversationStarters
getConversationStartersUseCase: mockGetConversationStarters,
playAudioUseCase: mockPlayAudio
)

sut.send(.viewAppeared)
Expand Down Expand Up @@ -199,7 +204,8 @@ extension ChatViewModelTests {
saveConversationUseCase: mockSaveConversation,
synthesizeSpeechUseCase: mockSynthesize,
getChatPreferencesUseCase: mockGetChatPreferences,
getConversationStartersUseCase: mockGetConversationStarters
getConversationStartersUseCase: mockGetConversationStarters,
playAudioUseCase: mockPlayAudio
)

sut.send(.viewAppeared)
Expand Down
Loading