feat: Son of Anton — native client for driving Claude against GitLab repos
Ground-up replacement for the DevPilot APK whose source was lost in the Sep 1
wipe. That app could only be maintained by guessing at an opaque binary: two
days went into inferring its wire contract from `strings` on libapp.so, and the
last bug (every text file rendering as "Binary file") was never solvable from
the server side at all. Owning the source ends that entire class of problem.
Five tabs, each backed by a real endpoint:
VS Code code-server in a webview, host derived from the API host
Chats multi-turn conversations, live SSE, per-message model and effort,
attachments from the phone, local notification when a run lands
Files project-scoped browser, re_editor with syntax highlighting,
markdown rendered with mermaid diagrams, create/rename/delete/upload
Git per-repo status, coloured diff, pull/commit/push
Settings Claude usage against both rolling windows, connection, sign out
Decisions worth recording:
- models.dart parses defensively on purpose. Every fromJson is total and a
wrong-typed field degrades to a default. The old client did the opposite and
one bad field made *every* file unopenable, because a throw inside fromJson
loses the whole object rather than one value.
- Binary is believed only when the server explicitly says so. Treating "unknown"
as binary is precisely the bug that made the previous app useless.
- SSE is read off a raw HttpClient, not Dio: it is a line protocol held open
indefinitely and Dio is built around a body that ends. The stream resumes with
?from=N so backgrounding the phone does not lose output.
- Mermaid renders in a webview because no Dart package draws it; the source is
passed as a JSON literal so quotes and newlines cannot break out, and it falls
back to showing the source offline instead of an empty box.
- Tabs live in an IndexedStack so scroll position, open directory and in-flight
streams survive switching away — the app exists to leave work running.
- No FCM. The server has nothing to push with, and the only event worth an
interruption is one the app already observes.
Co-Authored-By:
Claude Opus 5 (1M context) <noreply@anthropic.com>
parents
Showing
.gitignore
0 → 100644
.metadata
0 → 100644
README.md
0 → 100644
analysis_options.yaml
0 → 100644
android/.gitignore
0 → 100644
android/app/build.gradle.kts
0 → 100644
544 Bytes
442 Bytes
721 Bytes
1.01 KB
1.41 KB
android/build.gradle.kts
0 → 100644
android/gradle.properties
0 → 100644
android/settings.gradle.kts
0 → 100644
assets/.keep
0 → 100644
lib/core/api/models.dart
0 → 100644
This diff is collapsed.
lib/core/api/son_api.dart
0 → 100644
This diff is collapsed.
lib/core/notifications.dart
0 → 100644
lib/core/providers.dart
0 → 100644
lib/core/theme.dart
0 → 100644
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
lib/main.dart
0 → 100644
lib/widgets/empty_state.dart
0 → 100644
lib/widgets/flame_mark.dart
0 → 100644
pubspec.lock
0 → 100644
This diff is collapsed.
pubspec.yaml
0 → 100644