ft-tui 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. ft_tui-0.2.2/MANIFEST.in +4 -0
  2. ft_tui-0.2.2/PKG-INFO +496 -0
  3. ft_tui-0.2.2/README.md +479 -0
  4. ft_tui-0.2.2/pyproject.toml +39 -0
  5. ft_tui-0.2.2/setup.cfg +4 -0
  6. ft_tui-0.2.2/src/ft_tui/__init__.py +3 -0
  7. ft_tui-0.2.2/src/ft_tui/api/__init__.py +8 -0
  8. ft_tui-0.2.2/src/ft_tui/api/activity_types.py +120 -0
  9. ft_tui-0.2.2/src/ft_tui/api/campus_types.py +22 -0
  10. ft_tui-0.2.2/src/ft_tui/api/catalog_types.py +37 -0
  11. ft_tui-0.2.2/src/ft_tui/api/client_factory.py +17 -0
  12. ft_tui-0.2.2/src/ft_tui/api/client_interface.py +52 -0
  13. ft_tui-0.2.2/src/ft_tui/api/clubs_types.py +106 -0
  14. ft_tui-0.2.2/src/ft_tui/api/cluster_map_types.py +75 -0
  15. ft_tui-0.2.2/src/ft_tui/api/friends_types.py +204 -0
  16. ft_tui-0.2.2/src/ft_tui/api/goals_types.py +34 -0
  17. ft_tui-0.2.2/src/ft_tui/api/http_client.py +452 -0
  18. ft_tui-0.2.2/src/ft_tui/api/keychain.py +82 -0
  19. ft_tui-0.2.2/src/ft_tui/api/oauth_client.py +580 -0
  20. ft_tui-0.2.2/src/ft_tui/api/registration_types.py +106 -0
  21. ft_tui-0.2.2/src/ft_tui/api/slots_types.py +18 -0
  22. ft_tui-0.2.2/src/ft_tui/api/social_types.py +71 -0
  23. ft_tui-0.2.2/src/ft_tui/api/team_types.py +35 -0
  24. ft_tui-0.2.2/src/ft_tui/api/user_profile_types.py +153 -0
  25. ft_tui-0.2.2/src/ft_tui/cluster_map/__init__.py +1 -0
  26. ft_tui-0.2.2/src/ft_tui/cluster_map/experience.py +38 -0
  27. ft_tui-0.2.2/src/ft_tui/cluster_map/geometry.py +60 -0
  28. ft_tui-0.2.2/src/ft_tui/cluster_map/layout.json +1 -0
  29. ft_tui-0.2.2/src/ft_tui/cluster_map/state.py +93 -0
  30. ft_tui-0.2.2/src/ft_tui/config/__init__.py +1 -0
  31. ft_tui-0.2.2/src/ft_tui/config/settings.py +27 -0
  32. ft_tui-0.2.2/src/ft_tui/db/__init__.py +15 -0
  33. ft_tui-0.2.2/src/ft_tui/db/database.py +29 -0
  34. ft_tui-0.2.2/src/ft_tui/db/models.py +263 -0
  35. ft_tui-0.2.2/src/ft_tui/goto.py +97 -0
  36. ft_tui-0.2.2/src/ft_tui/i18n.py +95 -0
  37. ft_tui-0.2.2/src/ft_tui/integrations/__init__.py +1 -0
  38. ft_tui-0.2.2/src/ft_tui/integrations/zed_lsp.py +173 -0
  39. ft_tui-0.2.2/src/ft_tui/locales/es.json +1098 -0
  40. ft_tui-0.2.2/src/ft_tui/locales/fr.json +1115 -0
  41. ft_tui-0.2.2/src/ft_tui/main.py +72 -0
  42. ft_tui-0.2.2/src/ft_tui/services/__init__.py +1 -0
  43. ft_tui-0.2.2/src/ft_tui/services/catalog_cache.py +179 -0
  44. ft_tui-0.2.2/src/ft_tui/services/curriculum_selection.py +21 -0
  45. ft_tui-0.2.2/src/ft_tui/services/cursus.py +41 -0
  46. ft_tui-0.2.2/src/ft_tui/services/dashboard_cache.py +77 -0
  47. ft_tui-0.2.2/src/ft_tui/services/friends.py +165 -0
  48. ft_tui-0.2.2/src/ft_tui/services/fuzzy_search.py +77 -0
  49. ft_tui-0.2.2/src/ft_tui/services/git_service.py +92 -0
  50. ft_tui-0.2.2/src/ft_tui/services/pdf_checker.py +86 -0
  51. ft_tui-0.2.2/src/ft_tui/services/project_activity.py +49 -0
  52. ft_tui-0.2.2/src/ft_tui/services/project_catalog.py +21 -0
  53. ft_tui-0.2.2/src/ft_tui/services/project_checkout.py +191 -0
  54. ft_tui-0.2.2/src/ft_tui/services/project_curriculum.py +51 -0
  55. ft_tui-0.2.2/src/ft_tui/services/project_kind.py +15 -0
  56. ft_tui-0.2.2/src/ft_tui/services/project_navigation.py +144 -0
  57. ft_tui-0.2.2/src/ft_tui/services/project_presentation.py +93 -0
  58. ft_tui-0.2.2/src/ft_tui/services/project_registration.py +80 -0
  59. ft_tui-0.2.2/src/ft_tui/services/tester_catalog.py +127 -0
  60. ft_tui-0.2.2/src/ft_tui/services/tester_docker.py +437 -0
  61. ft_tui-0.2.2/src/ft_tui/services/tester_terminal.py +125 -0
  62. ft_tui-0.2.2/src/ft_tui/services/time_tracking.py +149 -0
  63. ft_tui-0.2.2/src/ft_tui/services/user_profile.py +42 -0
  64. ft_tui-0.2.2/src/ft_tui/services/user_settings.py +22 -0
  65. ft_tui-0.2.2/src/ft_tui/slots/__init__.py +1 -0
  66. ft_tui-0.2.2/src/ft_tui/slots/model.py +99 -0
  67. ft_tui-0.2.2/src/ft_tui/testers.toml +36 -0
  68. ft_tui-0.2.2/src/ft_tui/ui/__init__.py +1 -0
  69. ft_tui-0.2.2/src/ft_tui/ui/account_connection.py +239 -0
  70. ft_tui-0.2.2/src/ft_tui/ui/app.py +924 -0
  71. ft_tui-0.2.2/src/ft_tui/ui/button.py +238 -0
  72. ft_tui-0.2.2/src/ft_tui/ui/club_announcements.py +196 -0
  73. ft_tui-0.2.2/src/ft_tui/ui/club_inbox.py +50 -0
  74. ft_tui-0.2.2/src/ft_tui/ui/clubs.py +1007 -0
  75. ft_tui-0.2.2/src/ft_tui/ui/cluster_issue.py +234 -0
  76. ft_tui-0.2.2/src/ft_tui/ui/cluster_map.py +302 -0
  77. ft_tui-0.2.2/src/ft_tui/ui/cluster_map_grid.py +376 -0
  78. ft_tui-0.2.2/src/ft_tui/ui/cluster_portrait.py +295 -0
  79. ft_tui-0.2.2/src/ft_tui/ui/cluster_seat_preview.py +56 -0
  80. ft_tui-0.2.2/src/ft_tui/ui/cluster_workstation.py +493 -0
  81. ft_tui-0.2.2/src/ft_tui/ui/commands.py +138 -0
  82. ft_tui-0.2.2/src/ft_tui/ui/components.py +623 -0
  83. ft_tui-0.2.2/src/ft_tui/ui/correction_slots.py +319 -0
  84. ft_tui-0.2.2/src/ft_tui/ui/dashboard.py +599 -0
  85. ft_tui-0.2.2/src/ft_tui/ui/dev_log.py +263 -0
  86. ft_tui-0.2.2/src/ft_tui/ui/friend_project.py +227 -0
  87. ft_tui-0.2.2/src/ft_tui/ui/friend_search.py +129 -0
  88. ft_tui-0.2.2/src/ft_tui/ui/friend_suggestions.py +231 -0
  89. ft_tui-0.2.2/src/ft_tui/ui/friends.py +973 -0
  90. ft_tui-0.2.2/src/ft_tui/ui/layout.py +241 -0
  91. ft_tui-0.2.2/src/ft_tui/ui/modal.py +37 -0
  92. ft_tui-0.2.2/src/ft_tui/ui/navigation.py +27 -0
  93. ft_tui-0.2.2/src/ft_tui/ui/onboarding.py +249 -0
  94. ft_tui-0.2.2/src/ft_tui/ui/poke_notification.py +157 -0
  95. ft_tui-0.2.2/src/ft_tui/ui/profile_sections.py +124 -0
  96. ft_tui-0.2.2/src/ft_tui/ui/profile_view.py +171 -0
  97. ft_tui-0.2.2/src/ft_tui/ui/project_activity.py +400 -0
  98. ft_tui-0.2.2/src/ft_tui/ui/project_browser.py +176 -0
  99. ft_tui-0.2.2/src/ft_tui/ui/project_catalog_controller.py +288 -0
  100. ft_tui-0.2.2/src/ft_tui/ui/project_details.py +567 -0
  101. ft_tui-0.2.2/src/ft_tui/ui/project_navigation.py +173 -0
  102. ft_tui-0.2.2/src/ft_tui/ui/project_registration.py +229 -0
  103. ft_tui-0.2.2/src/ft_tui/ui/project_settings.py +146 -0
  104. ft_tui-0.2.2/src/ft_tui/ui/project_setup.py +183 -0
  105. ft_tui-0.2.2/src/ft_tui/ui/project_team.py +381 -0
  106. ft_tui-0.2.2/src/ft_tui/ui/project_technical_details.py +36 -0
  107. ft_tui-0.2.2/src/ft_tui/ui/project_testers.py +323 -0
  108. ft_tui-0.2.2/src/ft_tui/ui/push_events.py +97 -0
  109. ft_tui-0.2.2/src/ft_tui/ui/select.py +46 -0
  110. ft_tui-0.2.2/src/ft_tui/ui/settings.py +507 -0
  111. ft_tui-0.2.2/src/ft_tui/ui/shortcuts.py +77 -0
  112. ft_tui-0.2.2/src/ft_tui/ui/slots_calendar.py +233 -0
  113. ft_tui-0.2.2/src/ft_tui/ui/social.py +453 -0
  114. ft_tui-0.2.2/src/ft_tui/ui/styles/__init__.py +13 -0
  115. ft_tui-0.2.2/src/ft_tui/ui/styles/base.py +62 -0
  116. ft_tui-0.2.2/src/ft_tui/ui/styles/cluster_map.py +160 -0
  117. ft_tui-0.2.2/src/ft_tui/ui/styles/dashboard.py +98 -0
  118. ft_tui-0.2.2/src/ft_tui/ui/styles/dev_log.py +20 -0
  119. ft_tui-0.2.2/src/ft_tui/ui/styles/friends.py +45 -0
  120. ft_tui-0.2.2/src/ft_tui/ui/styles/projects.py +77 -0
  121. ft_tui-0.2.2/src/ft_tui/ui/styles/settings.py +35 -0
  122. ft_tui-0.2.2/src/ft_tui/ui/styles/user_profile.py +44 -0
  123. ft_tui-0.2.2/src/ft_tui/ui/terminal_capabilities.py +96 -0
  124. ft_tui-0.2.2/src/ft_tui/ui/user_profile.py +365 -0
  125. ft_tui-0.2.2/src/ft_tui/ui/username.py +26 -0
  126. ft_tui-0.2.2/src/ft_tui.egg-info/SOURCES.txt +123 -0
@@ -0,0 +1,4 @@
1
+ global-exclude *
2
+ include pyproject.toml README.md MANIFEST.in
3
+ recursive-include src/ft_tui *.py *.json *.toml
4
+ global-exclude __pycache__ *.py[cod] .env .env.* *.sqlite3 *.sqlite3-*
ft_tui-0.2.2/PKG-INFO ADDED
@@ -0,0 +1,496 @@
1
+ Metadata-Version: 2.4
2
+ Name: ft-tui
3
+ Version: 0.2.2
4
+ Summary: Offline-first terminal workspace for École 42 students
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: textual[syntax]<9.0,>=8.2
8
+ Requires-Dist: tree-sitter<0.26,>=0.25
9
+ Requires-Dist: tree-sitter-markdown<0.6,>=0.5
10
+ Requires-Dist: rich<15.0,>=13.7
11
+ Requires-Dist: SQLAlchemy<3.0,>=2.0
12
+ Requires-Dist: httpx<1.0,>=0.27
13
+ Requires-Dist: Pillow<14,>=11
14
+ Requires-Dist: textual-image[textual]<0.15,>=0.14.1
15
+ Requires-Dist: keyring<26.0,>=25.0
16
+ Requires-Dist: cryptography<46.0,>=42.0
17
+
18
+ # 42TUI
19
+
20
+ ## Refresh the Intra project graph
21
+
22
+ The `project_data.json` used by the local TypeScript graph tools is served by
23
+ the **Projects website**, which uses a browser session. An API OAuth bearer
24
+ token does not create that website session. To refresh the graph automatically:
25
+
26
+ 1. Install dependencies with `npm install`, then install the browser with
27
+ `npx playwright install chromium`. On a machine with a compatible Chromium
28
+ already installed, set `BROWSER_EXECUTABLE` instead.
29
+ 2. Copy `.env.example` to `.env` and set `INTRA_LOGIN`, `INTRA_PASSWORD`,
30
+ `CURSUS_ID`, and `CAMPUS_ID`. Keep `.env` private; it is gitignored.
31
+ 3. Run `bun fetch-graph-auto.ts`. The first login checks **Remember me** if
32
+ the 42 login page offers it. Later runs reuse the saved browser profile in
33
+ `.local/intra-graph-browser/`. If the session expires, the script logs in
34
+ again with the credentials in `.env`.
35
+
36
+ The output is `graph.<cursus>.<campus>.json`, which is gitignored. A failed
37
+ login or non-JSON response leaves the previous graph file in place. For a
38
+ scheduled server refresh, run the same command from the repository root with
39
+ a private `.env`; do not run overlapping jobs against the same browser profile.
40
+ If 42 asks for MFA or presents a challenge, the unattended login stops and
41
+ needs a fresh interactive authentication. `HEADLESS=false` opens a browser
42
+ for diagnosing a changed login flow.
43
+
44
+ 42TUI is an offline-first Python TUI for managing projects, work time, and notes for École 42
45
+ students. Local project data remains in SQLite. Optional 42 account connection uses a centralized
46
+ Bun API with PostgreSQL, Redis, browser-based OAuth, and device-bound sessions; the local app does
47
+ not receive the user's 42 password or OAuth client secret. See the [OAuth backend operations
48
+ runbook](docs/42-oauth-backend-operations.md) to deploy that service.
49
+
50
+ ## Getting started
51
+
52
+ ### Production installation
53
+
54
+ Once the installer service and first PyPI release are live, install on Linux/macOS:
55
+
56
+ ```sh
57
+ curl -fsSL https://tui.42wip.in/install.sh | sh
58
+ ```
59
+
60
+ The installer uses uv, obtains Python >=3.12 when needed (including on Ubuntu
61
+ 22.04 with system Python 3.10), and installs `42tui` in an isolated user environment.
62
+ Restart your terminal if needed, then launch `42tui`. Optional Nerd Font installation
63
+ is available with `--font`; you must select the font in your terminal settings.
64
+
65
+ Windows support is experimental; a PowerShell installer, exact-version installs,
66
+ upgrades, uninstall and migration from pipx are described in the
67
+ [production deployment guide](docs/production-deployment.md).
68
+
69
+ ### Development from this checkout
70
+
71
+ Python 3.12 or newer is required (including the textual-image dependency).
72
+
73
+ ```sh
74
+ python3.12 -m venv .venv
75
+ source .venv/bin/activate
76
+ pip install -r requirements.txt
77
+ 42tui
78
+ ```
79
+
80
+ For editable pipx installation, use the repository's `AGENTS.md` instructions
81
+ with an explicitly compatible Python interpreter.
82
+
83
+ The database is created under `$XDG_DATA_HOME/42tui/42tui.sqlite3` (or
84
+ `~/.local/share/42tui/42tui.sqlite3`). Existing projects remain available locally without an
85
+ account connection. Connect a 42 account in Settings to synchronize real projects from the
86
+ centralized API. Choosing **Save + Set Up Checkout** can clone the development repository URL
87
+ entered for a project. Uninstalling the application preserves this local data.
88
+
89
+ ## Project browsing
90
+
91
+ The catalogue uses official project IDs while SQLite keeps its own IDs for notes,
92
+ timers and checkout folders. Shared API metadata, the cursus/campus web graph and
93
+ account progression are stored separately. Choose the cursus and campus in Settings.
94
+ Common Core views display **old + shared** or **new + shared** exclusively. When the
95
+ available evidence cannot select a track, choose one in Settings. Empty quests do
96
+ not establish the old track or rank zero. Track preferences
97
+ are scoped to the account, cursus and campus.
98
+
99
+ **My 42 projects** shows account projects in the selected track and the known current
100
+ and next Common Core ranks. Rank resolution is automatic and uses quest evidence
101
+ first. When rank quests are absent, an active, unvalidated rank-0 project can infer
102
+ rank 0 from personal progress (`source: project`). This requires fresh personal
103
+ sources and a fresh graph for the selected cursus/campus, a resolved track, and
104
+ no validated rank-0 project, higher-rank attempt, specialization attempt or missing
105
+ classification. Empty history alone never implies rank 0. Saved manual ranks are
106
+ ignored. Project ranks are inferred from graph rules, not official API levels.
107
+ **All projects** shows every Common Core project in the selected track until a
108
+ quest confirms Common Core completion, then shows specialization projects. An
109
+ unknown personal rank still permits browsing the full Common Core catalogue.
110
+ **Registerable projects**
111
+ uses only the account's registration API response, including ID-only projects whose
112
+ catalogue description is unavailable. **Specializations** groups branches within the
113
+ selected track. **Active and local projects** keeps local work accessible when the
114
+ track is unknown. Search, type filters and pagination apply to the selected view.
115
+ In **My 42 projects**, choosing a specific type browses all matching projects in
116
+ the selected track, including later ranks and specializations, even when your
117
+ personal rank is unknown. Returning to **All types** restores the current/next
118
+ rank view. Other views keep their section, registration or local-work limits.
119
+ The browser starts in **My 42 projects**. Loaded projects are sorted by ascending
120
+ rank, with exams first within each rank, then by name.
121
+ Unranked projects appear last. Exams have a dedicated badge and accent border.
122
+ Click a rank header or press Enter on it to collapse or expand the group.
123
+ Group state is kept during the session
124
+ for each account, cursus/campus and view.
125
+ Rank headers show completed/total counts for loaded projects matching the filters.
126
+ Headers and project names use status colors: green for completed, accent for
127
+ active, amber for partial progress or available, red for failed, and muted for
128
+ inactive. Project status `unknown` is displayed as **INACTIVE**.
129
+
130
+ Details show exams, web difficulty as XP (including zero), nullable duration,
131
+ classification provenance, structured requirements, recommendations and raw
132
+ unparsed rules. Drawing links are visual relationships, not prerequisites.
133
+ Project details show Solo or Group when matching session metadata is available,
134
+ including in cached views. Known group sizes appear as **Group · 3 members**;
135
+ missing or conflicting session counts remain unspecified. **Register** submits directly through the API; 42 validates
136
+ eligibility even when team mode is unknown. API registration requires the `projects`
137
+ scope and fresh project history and catalog data. Registration refreshes status and
138
+ prevents duplicate submissions; creating or joining a team is a separate step.
139
+ The Intra project link remains available for the complete requirements.
140
+
141
+ The backend publishes validated API/browser snapshots through a separate ingestion
142
+ worker. TUI reads never start a browser. Catalogue and branch caches are scoped by
143
+ cursus, campus and snapshot; personal observations are isolated by account. Offline
144
+ views show saved data with its collection time and availability. Registration is
145
+ fresh for five minutes, progression/quests for fifteen minutes and catalogue for
146
+ 24 hours. An unavailable source preserves its prior observation as stale.
147
+
148
+ The TUI downloads all catalogue pages for both tracks and the unclassified section
149
+ of the selected cursus/campus into the existing SQLite database. Reopening the app
150
+ reuses that complete local copy for 24 hours; searches and project filters run
151
+ locally. Project details are saved on first selection and reused for the same
152
+ snapshot, including offline. Account progress still refreshes separately at startup.
153
+ Expired catalogues remain visible while a replacement downloads; failed or
154
+ interrupted downloads preserve the previous complete snapshot. No database reset
155
+ is needed for this cache.
156
+
157
+ The last known dashboard profile and goals are also saved locally. After a successful
158
+ sync, reopening with the same device credentials restores the selected cursus,
159
+ track, rank, Journey and account details before network refreshes complete. The
160
+ profile snapshot is bound to the backend and credential fingerprints; tokens and
161
+ private keys are never stored in this cache. Saved goals are marked stale while
162
+ refreshing, and pending or failed observations preserve the last known display.
163
+
164
+ To check the cache against synthetic HTTP responses, run
165
+ `postman simulation run postman/simulations/catalog-cache.sim.yaml --port 4901`
166
+ and, in another terminal,
167
+ `CATALOG_CACHE_SIMULATION_URL=http://127.0.0.1:4901/backend PYTHONPATH=src .venv/bin/python -m unittest tests.test_catalog_cache_simulation`.
168
+
169
+
170
+ The new SQLite schema refuses older databases with an explicit reset instruction.
171
+ Back up local notes and activity before intentionally deleting an old database;
172
+ the application never resets it automatically.
173
+
174
+ ## Project testers
175
+
176
+ In **Projects**, use the **Testers** button beside the project name or press `t`
177
+ to reveal and focus the tester controls. The **Testers** section offers **Install Francinette**, then
178
+ **Run Francinette**, for `libft`, `ft_printf` and `get_next_line` (including their
179
+ `42cursus-` slugs). One image serves all three projects. Installation needs no
180
+ project folder; running needs an existing local folder configured with `e`.
181
+ Born2beroot has no bundled tester yet.
182
+
183
+ 42TUI fetches a pinned commit of
184
+ [WaRtr0/francinette-image](https://github.com/WaRtr0/francinette-image) and builds
185
+ its Dockerfile, without running its installer or editing shell configuration.
186
+ Docker uses `linux/amd64` or `linux/arm64` on Linux and macOS with x86_64
187
+ (Intel/AMD) or ARM64 processors. Francinette prefers the Docker engine's native
188
+ architecture, avoiding x86_64 emulation on ARM64. This also respects the CPU of
189
+ a remote Docker engine. 32-bit x86 is unsupported.
190
+ Docker Engine, Docker Desktop and other compatible local engines use the standard
191
+ Docker CLI. The active context and `DOCKER_*` variables are respected and kept
192
+ for the entire operation, including cleanup. A Linux engine is required.
193
+ Before running, 42TUI checks the selected container architecture and reads/writes a temporary probe
194
+ inside the project folder to verify local file sharing and permissions.
195
+ On Linux, a regular daemon runs testers with your UID/GID; rootless Docker retains
196
+ its mapped container root. Francinette uses a private writable copy of its code
197
+ for logs and caches, so generated project files remain owned by your account.
198
+ During each run, private Clang wrappers default debug information to DWARF 4,
199
+ compatible with the upstream Ubuntu 22.04 image's Valgrind. Memory checks remain
200
+ enabled; compiler arguments and explicit debug-format options are preserved.
201
+ The wrappers and writable tester copy are removed when the run finishes.
202
+ The upstream Dockerfile fetches dependencies at build time; pinning the wrapper
203
+ repository does not freeze those dependencies. The temporary container mounts the
204
+ project read/write at `/project` and runs Francinette from its writable copy,
205
+ keeping `/project` as the working directory.
206
+ Tester output appears in the terminal; press any key to return to 42TUI. Ctrl+C
207
+ interrupts the operation and removes its temporary container. An exit status of
208
+ zero means the tester finished; inspect its output for individual test results.
209
+ Logs are saved under `$XDG_DATA_HOME/42tui/testers/logs`, or
210
+ `~/.local/share/42tui/testers/logs`. Failed operations offer **Retry**.
211
+
212
+ The small **×** button beside a tester removes its Docker images (all installed
213
+ versions and CPU architectures) and stopped 42TUI test/probe containers. It also
214
+ works without a configured project folder. Since the installation is shared,
215
+ removing Francinette affects all three projects; use **Install Francinette** to
216
+ restore it. Running containers, project files, other testers and Docker volumes
217
+ are left in place. Docker can retain shared layers and build cache, so removing
218
+ the images does not guarantee reclaiming their entire displayed size. See
219
+ [Docker's resource cleanup documentation](https://docs.docker.com/engine/manage-resources/pruning/).
220
+
221
+ 42TUI reuses working Docker installations. Otherwise it distinguishes a missing
222
+ CLI, a stopped engine and denied access. **Check Docker access again** retries
223
+ detection after setup. On macOS, **Install Docker** offers Homebrew when available,
224
+ or downloads and opens the official installer for the host processor. Finish
225
+ [Docker Desktop setup](https://docs.docker.com/desktop/setup/install/mac-install/)
226
+ yourself and start Docker before rechecking. On supported Ubuntu and Debian
227
+ releases, the confirmation dialog lists the commands for Docker's official apt
228
+ repository before any administrator command runs. Conflicting system packages
229
+ must be resolved manually; 42TUI does not remove them. Other distributions link to
230
+ the official installation instructions. For access problems, follow the
231
+ [Linux post-install steps](https://docs.docker.com/engine/install/linux-postinstall/),
232
+ then sign out and back in; never run the entire application with `sudo`.
233
+
234
+ The bundled `ft_tui/testers.toml` is loaded at startup and extended by
235
+ `$XDG_CONFIG_HOME/42tui/testers.toml`, falling back to `~/.config/42tui/testers.toml`.
236
+ Each user entry replaces the entire entry with the same `id`; multiple entries
237
+ may target a project. Disable a bundled tester with:
238
+
239
+ ```toml
240
+ [[testers]]
241
+ id = "francinette"
242
+ enabled = false
243
+ ```
244
+
245
+ To add a tester, copy this template and replace the repository, full commit SHA,
246
+ Docker build context and command with real values. The command is an argument
247
+ array executed inside the container, without shell expansion. Repository URLs must
248
+ use HTTPS, context paths must remain inside the repository, and platforms are
249
+ `linux` and/or `darwin`:
250
+
251
+ ```toml
252
+ [[testers]]
253
+ id = "my-tester"
254
+ name = "My tester"
255
+ projects = ["libft", "42cursus-ft_printf"]
256
+ platforms = ["linux", "darwin"]
257
+ repository = "https://github.com/your-account/your-tester.git"
258
+ ref = "0123456789abcdef0123456789abcdef01234567"
259
+ context = "."
260
+ command = ["/tester/run", "--verbose"]
261
+ ```
262
+
263
+ Restart 42TUI to reload changes. Invalid user TOML or entries display an error and
264
+ leave the complete bundled catalog usable. Image identities include the tester,
265
+ commit, repository and context; availability is always checked against Docker.
266
+ ARM64 images have a separate tag, so existing amd64 images are preserved.
267
+ On ARM64, install the native image once from the tester controls after upgrading.
268
+ Optional `architectures = ["amd64", "arm64"]` allows native execution for custom
269
+ testers that support both CPUs. Existing custom entries default to `["amd64"]`;
270
+ a tester restricted to one CPU still requires emulation on other CPUs. When the
271
+ engine's CPU is unavailable, the first configured architecture is used.
272
+ See the [verification report](docs/testers-validation.md) for tested environments
273
+ and remaining platform checks.
274
+
275
+ ## Project checkout setup
276
+
277
+ In **Projects**, select a project and press `e`. Choose its local folder and optional
278
+ development remote, then select **Save + Set Up Checkout**. 42TUI clones the development remote
279
+ into a missing or empty folder, or initializes an empty local Git repository when no development
280
+ remote is set. Existing repositories are preserved; non-empty non-Git folders and conflicting
281
+ remote URLs are rejected.
282
+
283
+ When the checkout has commits, setup ensures local `dev` and `master` branches. A cloned checkout
284
+ switches to `dev`, associated with `origin/dev`. `master` is reserved for Vogsphere, whose accepted
285
+ submission branch is `master`; once the Intra API supplies its URL, 42TUI configures
286
+ `vogsphere/master`. No branch is pushed automatically. Add a real development URL yourself if you
287
+ want setup to clone it. For a new empty local repository, Git cannot store a
288
+ `master` branch before the first commit; run setup again after creating that commit to create
289
+ `master`.
290
+
291
+ In project details, **Start timer** records a manual work session in SQLite. A running timer
292
+ survives closing and reopening 42TUI; starting a timer for another project stops the previous
293
+ one. Subject checks run at the interval selected in Settings (six hours by default). 42TUI looks
294
+ for `subject.pdf` at the project root, calculates its SHA-256, and asks the configured provider
295
+ for a reference fingerprint. The backend queues subject work for the unified Intra worker,
296
+ which uses the same authenticated browser profile as Graph and Pace. It discovers the subject
297
+ link on the project page, prefers English, validates the PDF, and caches it with its SHA-256
298
+ in Redis. The HTTP handler never opens a browser or calls the restricted attachments endpoint.
299
+ The TUI shows `SUBJECT · QUEUED` while waiting and checks for the result every 30 seconds.
300
+ Missing or different PDFs are atomically installed as `subject.pdf`; failed downloads preserve
301
+ the existing file. Configured folders work with or without a Git repository. Worker snapshots
302
+ are fresh for six hours and retained for 24 hours. PDF contents are not extracted.
303
+ Project setup adds `*.pdf` to `.gitignore` so a downloaded untracked
304
+ subject isn't accidentally committed; PDFs already tracked by Git remain tracked until removed manually.
305
+ Use **Open subject** to launch the PDF with the OS default application or TDF, selected in Settings.
306
+ TDF must be installed separately and available on `PATH`.
307
+
308
+ ## Keyboard shortcuts
309
+
310
+ - `j` / `k`: move down or up through projects
311
+ - `h` / `l`: switch to the previous or next tab
312
+ - `e`: set up or edit the selected project's local path and development remote
313
+ - `Home` / `End`: select the first or last project
314
+ - `Ctrl+D` / `Ctrl+U`: move down or up one page
315
+ - `F1`–`F5`: help, projects, notes, Git Sync, and settings
316
+ - `q`: quit
317
+
318
+ In **Settings**, choose a favorite editor (Neovim, Vim, Visual Studio Code, or Zed) and a favorite
319
+ Textual theme. From **Projects**, select a configured project and press `o` or use **Open in editor**
320
+ to open its folder. Terminal editors temporarily take over the terminal; GUI editors open separately.
321
+
322
+ In **Settings → Appearance**, image rendering defaults to **Automatic**: 42TUI uses
323
+ the Sixel or Kitty graphics backend selected by `textual-image`, with colored Unicode
324
+ blocks as a fallback. You can force either protocol, select colored blocks, or turn
325
+ images off. Forced protocols require support in the terminal and any multiplexer
326
+ between it and 42TUI. Rendering is selected by protocol support, not CPU/GPU hardware.
327
+
328
+ Icons have independent **Automatic**, **Nerd Font**, **Simple Unicode**, and **Text only**
329
+ modes, with a preview. Automatic mode uses terminal/environment hints, not a scan of
330
+ installed fonts: those do not establish which font the terminal uses, especially over
331
+ SSH. `NERDFONT=1` or `NERDFONT=0` overrides the automatic estimate. An explicit icon
332
+ choice in Settings takes precedence. These preferences are saved locally and apply
333
+ without restarting; font shape and pixel size remain controlled by the terminal.
334
+
335
+ ## `42goto` shell navigation
336
+
337
+ `42goto` changes the current shell directory to a project's configured local folder. Enable the
338
+ shell function and project completion in your shell startup file:
339
+
340
+ ```sh
341
+ # Bash: add to ~/.bashrc
342
+ eval "$(command 42goto --shell-init bash)"
343
+
344
+ # Zsh: add to ~/.zshrc
345
+ eval "$(command 42goto --shell-init zsh)"
346
+ ```
347
+
348
+ Then run `42goto` to jump to the currently active project, or `42goto <project>` to open a project
349
+ by its name or slug. Press Tab to complete configured project slugs. A fresh editor heartbeat
350
+ defines the active project; when there is no fresh heartbeat, the most recently tracked project
351
+ is used. If there is no activity history, a single configured in-progress project is used; otherwise
352
+ 42goto asks you to specify the project. Project folders are read from the same local SQLite database
353
+ as 42TUI. After updating a source checkout, reinstall the package to refresh the CLI entry points:
354
+
355
+ ```sh
356
+ pip install -e .
357
+ ```
358
+
359
+ ## Editor activity tracking
360
+
361
+ 42TUI accepts local activity heartbeats from editor plugins, even when the TUI is closed. Configure
362
+ the project folder in 42TUI first, then have an editor integration call the shared CLI command when
363
+ the active file changes, is edited, or is saved:
364
+
365
+ ```sh
366
+ 42tui heartbeat --file /absolute/path/to/project/src/main.c --editor nvim --quiet
367
+ 42tui heartbeat --file /absolute/path/to/project/src/main.c --editor vscode --write --quiet
368
+ ```
369
+
370
+ The CLI finds the deepest configured project folder containing the file. It stores the project,
371
+ editor, event time, and save flag in SQLite; it does not store the file path or read file contents.
372
+ Elapsed time is attributed between recent heartbeats, with a 120-second idle cutoff and a shared
373
+ timeline across editors to prevent double counting. Files outside configured project folders are
374
+ ignored with an error. Editor-specific plugins can call this command, so each integration only
375
+ needs to report its active file and editor ID; installing the app itself requires no network access.
376
+ See [`integrations/`](integrations/README.md) for the current VS Code, Zed, Vim, and Neovim adapters.
377
+
378
+ ## Roadmap
379
+
380
+ Implemented for the local MVP:
381
+
382
+ - Local project catalog, dashboard, and SQLite persistence; centralized API project sync after
383
+ OAuth connection
384
+ - Profile settings, default projects folder, and per-project local path / development remote setup
385
+ - Project navigation, Git hygiene, manual timer, PDF fingerprinting, and Markdown dev log
386
+ - Local editor heartbeat ingestion and project-level coding time aggregation
387
+ - Dashboard activity streaks, four-week heatmap, peak coding day/hour, and rolling validation pace against Black Hole deadlines
388
+ - Editor integrations for VS Code, Zed, Vim, and Neovim
389
+ - First-run onboarding for profile, projects folder, editor, theme, and language
390
+ - English, French, and Spanish interface with JSON catalogs and English fallback
391
+
392
+ Available with the optional OAuth backend:
393
+
394
+ - Browser-based 42 account pairing and profile/project synchronization, while preserving local SQLite data
395
+
396
+ Planned:
397
+ - Fetch and display achievement stars for outstanding project evaluations
398
+ - Automatically fetch the official Vogsphere remote from 42 Intra
399
+ - **Peer-Review Matcher:** use the 42 API to show available defense slots, consider correction points and availability, notify about useful openings, and keep an evaluation history with grades and feedback
400
+ - **Cluster Map:** simulate the campus's three clusters as a seat map, showing per-seat occupancy and available seats, plus the 42 profile and project information available for each seated student
401
+ - **Friends & Study Groups:** use the 42 API to automatically sync friends' project enrollment and progress, with activity and completion feeds, Black Hole pace comparisons, and cluster presence
402
+
403
+ ## Source layout
404
+
405
+ Run the service regression tests from the project root after installing the package:
406
+
407
+ ```sh
408
+ python -m unittest discover -s tests -v
409
+ ```
410
+
411
+ ```text
412
+ src/ft_tui/
413
+ ├── api/ # centralized API client and provider contract
414
+ ├── config/ # local paths and settings
415
+ ├── db/ # models and SQLite access
416
+ ├── services/ # business logic independent from the UI
417
+ ├── ui/ # Textual application and views
418
+ ├── locales/ # English-fallback JSON catalogs (French and Spanish)
419
+ └── main.py # CLI entry point
420
+ ```
421
+
422
+ To add another interface language, add `src/ft_tui/locales/<code>.json` with
423
+ English source messages as keys, then register its code and display name in
424
+ `ft_tui/i18n.py`. Any untranslated message falls back to English.
425
+
426
+ ### Project attempts and evaluations
427
+
428
+ For a connected account, select a project and open **Attempts & evaluations**
429
+ from Project Details. The dedicated screen selects the current team and lets you
430
+ browse previous attempts, members, scores, repository and lock/closure dates.
431
+ Expand peer corrections to read comments and available feedback; expand automated
432
+ evaluations to read their output. Scheduled, overdue/unfilled and completed
433
+ corrections have separate labels. Missing marks remain distinct from zero.
434
+
435
+ Use arrow keys to select an attempt, Tab/Enter to expand an evaluation, `r` to
436
+ refresh, `i` to open Intra, and Escape to return to the selected project. The
437
+ layout stacks on terminals narrower than 90 columns. Activity, evaluation details,
438
+ automated output and embedded team results are cached by account for up to 30 days. Reopening the screen reuses these observations;
439
+ press Refresh to fetch changes from Intra. Cached sources show their collection
440
+ time separately; unavailable fields and hidden participants stay explicit.
441
+ Team management, booking and feedback submission remain planned in `TODO.md`.
442
+
443
+ Both the updated backend and TUI are required. Live 42 response parity remains
444
+ unverified. See [the HTTP simulation instructions](postman/PROJECT_ACTIVITY_SIMULATION.md)
445
+ for local integration verification and the sandbox limitation.
446
+
447
+ ## Friends and rankings
448
+
449
+ Connect your 42 account and select a cursus in Settings, then open Friends.
450
+ Add friends by their 42 login to see their level, ongoing projects and recent
451
+ cluster location. Open a friend for project history or “Compare with me” to find
452
+ shared projects and opportunities to help each other. Rankings compares cursus
453
+ working curriculum ranks among your friends and yourself, with equal places for tied ranks. Published old/new cursus project mappings determine the estimate; level remains visible.
454
+
455
+ Search and filters cover shared projects, campus membership and active cluster
456
+ sessions. Enter opens a profile; Escape returns to the list. Narrow terminals
457
+ show one pane at a time. Saved progression remains available offline, while
458
+ cluster presence always requires a recent successful lookup. Refresh runs in the
459
+ background while the tab is open; progression caches refresh every 15 minutes
460
+ and presence every minute. The updated backend must be deployed before using
461
+ online Friends. Campus rankings and historical growth remain follow-up work in
462
+ [TODO.md](TODO.md).
463
+
464
+ Search in Friends and Projects accepts abbreviations and small typos, ignores
465
+ case, accents and separators, and supports multiple words in any order. For
466
+ example, `pswp` or `puhs swap` finds `push_swap`. Friends search also matches
467
+ display names, campuses and ongoing projects; rankings retain their original
468
+ positions when filtered. Project search behaves consistently offline and in
469
+ catalog results. Deploy the updated backend to enable fuzzy catalog queries.
470
+
471
+ The Add friend field searches a shared student directory stored in the backend.
472
+ Type at least two characters, press Down to browse matches and Enter to choose
473
+ a login, then select Add friend. It matches names, abbreviations and small typos,
474
+ excludes existing friends and your account, and works across campuses. Results
475
+ show when the index is building or stale. Selecting Add verifies the numeric user
476
+ ID and handles renamed logins. Exact-login entry remains available. Deploy the
477
+ new migration and run the [directory worker](backend/README.md#add-friend-suggestions)
478
+ to populate and refresh the index; typing never scans the 42 API.
479
+
480
+
481
+ ## Clubs
482
+
483
+ Open Clubs to create an invite-only campus group, add registered 42TUI students,
484
+ and recruit players with Babyfoot, Smash or custom templates. Members join or
485
+ waitlist; organizers start ready activities manually. Club managers and configured
486
+ campus representatives can publish announcements.
487
+
488
+ The global DND button (`Ctrl+Shift+D`) suspends Clubs notifications. Each club
489
+ has its own bell; added members and the campus group start muted. Invitations
490
+ appear at the top right without taking focus (`Ctrl+Shift+N` focuses them).
491
+ Activities and announcements remain browsable while muted. Spontaneous popups
492
+ require fresh campus presence on an unlocked workstation.
493
+
494
+ Apply the Clubs database migration and restart both backend and TUI before use.
495
+ See the [MVP guide](docs/clubs-mvp.md) for deployment, current limitations and
496
+ verification, and the [roadmap](docs/clubs-roadmap.md) for later phases.