Week 15 — Interface and application programming

Week 15 follows Interface and Application Programming. On the individual assignment I wrote a UI for my own ESP32-WROOM board: a 2.4″ ILI9341 TFT (SPI) plus FT6336 capacitive touch (I²C). I started with one combined sketch, hit GPIO12 / wrong-touch-chip problems, then split display and touch into two test programs before porting five swipe pages to firmware. For the final-project plant companion I also made a 320×240 HTML mock-up, connected my Week 8 XIAO sensor board to the WROOM display over I²C, and wired open Mandarin speech through Alibaba Bailian ASR/LLM/TTS (§11). Code is in code/week15-individual/final/ and code/week17-individual/; the group assignment compares Arduino IDE, Thonny, VS Code, and MATLAB.

Downloads — source code and UI files

Bench setup this week: ESP32-WROOM-32D + 2.4″ ILI9341 (SPI) + FT6336 touch (I²C). Use Download if the browser opens a file instead of saving it.

Display + sensor UI demo (XIAO + WROOM display):

▶ Open on-device UI video — five-panel interface running on the TFT

▶ Open voice dialogue demo — full speech conversation with chat bubbles on the TFT

↓ Browse code/week15-individual/final/

↓ Browse wroom-display/ — five-page TFT UI (PlatformIO, env:esp32dev)

Display firmware (wroom-display/src/, notes in §7d):

↓ Download main.cpp

↓ Download env_i2c_slave.cpp

↓ Download env_i2c_slave.h

↓ Download i2c_env_link.h

↓ Download board_pins.h

↓ Download ft6336_touch.h

↓ Download platformio.ini

↓ Browse s3-hub/ — XIAO sensor + WiFi + DeepSeek

Sensor hub firmware (s3-hub/src/):

↓ Download main.cpp · ↓ Download i2c_env_link.h · ↓ Download platformio.ini

Earlier bring-up sketches (display / touch split tests):

↓ Download main_test_screen.cpp

↓ Download main_test_touch.cpp

UI mockups (320×240 HTML, five swipe panels):

↓ Download week15-smart-plant-companion-lcd-ui.html

↗ Open HTML prototype in new tab

↗ Open screen-ui.html — same panel order as firmware

Wiring notes:

↓ Download i2c_wiring_notes_zh.txt

Voice / Bailian cloud firmware (§11):

↓ Browse code/week17-individual/ — push-to-talk mic, Bailian WSS client, TFT chat page

↓ Browse week17-individual/README.md

Individual assignment

The brief asks for an application on a board I designed, with input and/or output devices. My UI is the ILI9341 + FT6336 stack on the WROOM display PCB from Week 10. Main evidence is the screen harness on that PCB (§3) and the five live TFT pages in §7d.

Fab Academy interface-programming checklist mapped to this page
Nueval item Where on this page
Linked group assignment Group assignment + §10 reflection
Documented process §1–7 (sketch → split tests → HTML → firmware → board link)
Explained the UI and how I built it §6, §7d
How the app talks to my microcontroller board §3 (harness), §7d (SPI / I²C)
Problems encountered and fixes §1 (GPIO12 reset, resistive-touch assumption), §7 (I²C data path and redraw behavior)
Original source code Downloads, §8
Hero shot — app running with the board On-device video + five TFT pages (§7d)
Voice / cloud dialogue loop §11 (Bailian console, mic/speaker, demo video)

1) Task and starting idea

I wanted one firmware image that showed the words Fab Academy with a tappable button below, exercising both the panel and the touch controller in a single pass. I used an AI assistant inside Cursor to draft that combined sketch. In practice the button graphic did not refresh reliably and the Serial monitor stayed quiet when I tapped, so I stopped guessing and split the work into two programs: display-only bring-up, then touch-focused bring-up. That separation made it obvious whether SPI wiring, reset timing, or the touch stack was the weak link.

Screen showing Fab Academy text and a button region from an early combined sketch
Early combined sketch (Cursor-assisted): title + button area before I split display and touch tests.
First on-device attempt at the combined Fab Academy and button UI
First on-device attempt at the all-in-one UI.
Second attempt still without working touch feedback
Second attempt: display progressed, but touch still did not produce usable feedback in the monitor.

2) Learning (pins, controllers, and references)

ILI9341 over 4-wire SPI was the baseline: chip-select, data/command, SPI clock/MOSI (and MISO where the library or readback path needs it), plus backlight and reset. The vendor shipped an STM32-oriented example; I ported the drawing tests to ESP32-Arduino with Adafruit_ILI9341 + Adafruit_GFX, keeping their gamma tables and rotation mapping where it helped this particular IPS panel look correct.

The bigger lesson was strapping pins on the ESP32. I first tied the LCD RST line to GPIO12 because that matched a convenient label on a pinout card. The panel stayed dark even though SPI traffic looked plausible in code. Digging into Espressif’s documentation, GPIO12 is also MTDI, a strapping pin that is sampled at chip reset to configure the flash voltage behavior of the VDD_SDIO domain. At reset, the voltage expected on that strapping state must match the flash fitted on the module (the common WROOM modules use 3.3 V flash). If GPIO12 is driven or pulled to the wrong level during that sampling window, boot or flash access can fail or behave intermittently. Many reference designs treat GPIO12 as risky for outputs such as an active-low reset that might sit in the wrong state at power-up. After I moved TFT_RST to a normal, non-strapping GPIO (and kept the net short and clean), the ILI9341 initialization sequence finally ran as expected.

Primary references I used while writing this up: ESP32 datasheet (strapping pins / MTDI) and Espressif’s boot mode / strapping overview.

For touch, the panel is capacitive with an FT6336 controller, not a resistive XPT2046 on bit-banged SPI. The first AI-generated sketch assumed resistive SPI touch, which never matched the wiring on my FPC. Switching to an I²C driver path (address commonly 0x38, with I²C SDA/SCL, optional INT, and a controller reset pin) matched the vendor examples and started returning coordinates. In the sketch I ship here, the bring-up messages assume SDA on GPIO22, SCL on GPIO18, and a CTP reset on GPIO5 (see the comments in main_test_touch.cpp and your local board_pins.h).

ESP32-WROOM-32D module on the bench
ESP32-WROOM-32D module used for the week.
Vendor pinout photo for the LCD flex and breakout
Vendor photo: flex/breakout pin naming; I mapped their STM32 labels to ESP32 GPIOs from this sheet.
Handwritten GPIO plan mapping SPI, reset, backlight, and touch I2C
My working map: SPI, backlight, TFT reset (not on GPIO12), and touch I²C pairs.

Before I trusted the application UI, I tested the capacitive touch layer by itself. These two photos belong to the testing stage: first I checked whether the FT6336 returned stable reads, then I drew trails to confirm the I²C path, coordinate mapping, and refresh loop.

Capacitive touch bring-up on the ILI9341 panel
Capacitive touch bring-up: verifying FT6336 reads before trusting UI layers.
Red finger-drawn trails on the white LCD background
Successful capacitive test: drawing trails proves the I²C path, coordinate mapping, and refresh loop line up.

3) Main hardware evidence — screen harness on my own WROOM PCB

The important hardware story for this week is not the temporary breadboard. My application is meant for the screen-facing WROOM board I designed for the final project. I had already documented the physical screen route in Week 10: the TFT sits on the front of the pot, the harness passes through the shell, and the wires land on the pad groups of my WROOM display PCB. I am copying those photos here because they explain the real board-to-screen connection better than the debugging bench photo.

The Week 10 photos were taken during the ST7789 output-device build, while this week’s debug panel is ILI9341-class. I keep the distinction clear: the photos prove the physical screen harness to my PCB; the Week 15 code below proves the application UI, touch input, SPI display output, and I²C communication.

EasyEDA layout of the custom WROOM display PCB with screen wire pad groups
Designed board: EasyEDA export from Week 10. The WROOM module footprint and the highlighted screen pad groups are the board-side provision for the TFT harness.
Annotated WROOM PCB solder pads showing where the TFT display wires connect
Screen-to-board close-up: the TFT harness lands on my WROOM PCB pads. I used this photo to check the physical wires against the firmware pin names before trusting the UI code.
TFT harness soldered to pad groups on the custom WROOM display PCB
Board connection completed: screen wires soldered to the custom WROOM display PCB in the base. This is the connection path I want the Week 15 application to represent.
TFT wires passing through the flowerpot toward the electronics base
Harness route: the screen is mounted where the user sees it, while the wires pass through the pot toward the WROOM board in the base.

I still used temporary wiring during the Week 15 ILI9341/FT6336 bring-up because it made wrong reset pins and touch-bus assumptions easier to isolate. I do not use that as the main photo evidence here. The assessed wiring story is the board-side screen connection above, plus the application firmware that drives the display, reads touch, and exchanges data over I²C.

4) Plan

  1. Stabilize 3.3 V, GND, and SPI signals; confirm begin() succeeds.
  2. Run the vendor-style color-bar and fill tests to prove the glass is alive.
  3. Probe the touch controller on I²C, then map raw coordinates into display space.
  4. Capture photos and a short clip for documentation; keep two .cpp entry points so display and touch stay easy to retest.

5) Operation and evidence

After I moved TFT reset off GPIO12, the display-only sketch ran the vendor colour bars and fill tests. The touch sketch then drew red trails and printed coordinates on Serial when a finger stayed down. Before the harness sat on my Week 10 PCB, I used a breadboard rig so I could swap reset pins and touch-bus wiring without reheating solder joints.

Breadboard bring-up of ESP32-WROOM with ILI9341 display and FT6336 touch wiring
Breadboard bring-up: temporary wiring while I isolated GPIO12 reset and resistive-vs-capacitive touch mistakes.
On-device UI test: the five-panel interface running on the TFT, after I moved from the HTML prototype into MCU firmware.

6) LCD UI mockups (sketch → HTML → on-device)

I wanted to see layout at the real 320×240 pixel size before writing C++. I drew a paper sketch, described each panel in short prompts, and had Cursor generate one HTML file with five horizontal swipe panels. The copy is in Chinese because that matches how I talk to the plant-companion character; this write-up stays in English.

Hand-drawn wireframe for the smart plant companion LCD UI
Design sketch: block layout before any code existed.

UI prototype file — 320×240 HTML, five horizontal swipe panels sized for the ILI9341 canvas.

If the browser previews instead of saving, use Download or Save As….

↓ Download week15-smart-plant-companion-lcd-ui.html

↗ Open HTML prototype in new tab

Five PNG exports (one per panel):

AI-generated smart plant UI screen export 1 of 5
Screen 1 of 5 — exported from the HTML prototype.
AI-generated smart plant UI screen export 2 of 5
Screen 2 of 5.
AI-generated smart plant UI screen export 3 of 5
Screen 3 of 5.
AI-generated smart plant UI screen export 4 of 5
Screen 4 of 5.
AI-generated smart plant UI screen export 5 of 5
Screen 5 of 5.

7) How the UI code works

I used Cursor to code the HTML swipe prototype, the split test sketches (main_test_screen.cpp, main_test_touch.cpp), and the first passes of wroom-display/src/main.cpp. I rewrote pin assignments, reset timing, and FT6336 touch handling after each hardware test — the model once assumed resistive SPI touch and the wrong reset pin.

The UI runs on the WROOM display PCB from Week 10. Sensor data and chat text come from my Week 8 XIAO over the host-link I²C connection.

Steps I actually took

  1. Paper sketch → 320×240 HTML swipe file (§6, screen-ui.html).
  2. Checked Week 10 photos so firmware pin names matched the harness (§3).
  3. Display-only test in main_test_screen.cpp, then touch in main_test_touch.cpp.
  4. Copied each HTML panel into a drawPage…() function in wroom-display/src/main.cpp.
  5. Let the XIAO push sensor readings, network state, and chat text over I²C.

Drawing the five pages

There is no GUI framework on the MCU. Each frame I call Adafruit GFX shapes and text on top of Adafruit_ILI9341. Five functions match the HTML panels:

  • Page 0 — emoji face from DHT temperature (data from the XIAO over I²C).
  • Page 1 — plant status row + clock line.
  • Page 2 — humidity, temperature, light bars.
  • Page 3 — brightness/volume sliders and three toggle buttons.
  • Page 4 — chat bubbles when voice dialogue runs.

Variable s_page picks which drawer runs. Horizontal swipes change pages after the finger lifts; on the control page I handle slider drags first so they do not accidentally flip pages. Colours and order follow screen-ui.html.

How numbers and text get updated

Touch state (page index, slider values, toggle flags) lives in main.cpp. Incoming I²C bytes are queued in env_i2c_slave.cpp on a second I²C port (Wire1) so they do not fight the touch chip on Wire. Fixed 8-byte blocks carry humidity and temperature; longer packets carry chat lines, WiFi status, and clock strings (format in i2c_env_link.h). When I tap control buttons, syncHostUiStatus() sends a short status block back so the XIAO can adjust dialogue tone. The main loop only redraws a page when its data version counter changes, so the screen does not flicker on every bus byte.

SPI to the TFT, I²C to touch and other boards

The ILI9341 gets pixels over 4-wire SPI at 40 MHz (TFT_SPI_HZ in board_pins.h). I kept the vendor gamma table and rotation mapping because this IPS panel looked wrong without them. Backlight is PWM on GPIO25; the control-page slider calls backlightSetPct() live.

Touch uses Wire at 400 kHz to FT6336 address 0x38. Host-link I²C uses Wire1 on GPIO19/21 (see board_pins.h) so touch polling and sensor traffic stay on separate drivers.

Bus summary

WhatWiresDirectionFiles
TFT SPI + D/C + CS + RST WROOM → screen pixels main.cpp, board_pins.h
Touch I²C on Wire FT6336 → swipe/tap ft6336_touch.h
Sensor + chat data I²C slave 0x55 on Wire1 XIAO → WROOM env_i2c_slave.cpp, i2c_env_link.h
Control toggles I²C read by XIAO WROOM → XIAO syncHostUiStatus() in main.cpp
Debug UART 115200 Each board → PC Serial monitor

XIAO firmware: s3-hub/.

Libraries and tools I used

Tool / libraryWhy
PlatformIO + Arduino on espressif32Build and upload WROOM and XIAO firmware
Adafruit GFX + ILI9341Draw the five TFT pages
ESP32 Wire / Wire1Touch on one port, sensor link on the other
VS Code / CursorEdit repo; Cursor only for the first HTML draft
HTML mock-up filesLayout reference before embedding

On-board photos (hero evidence)

All shots below are from the real TFT, not a desktop simulator. The harness-to-PCB story is in §3; these bench photos make the five pages easier to read.

Integrated board and debug evidence
TFT screen connected to the custom WROOM display PCB with the UI running
TFT screen connected to my WROOM display PCB while the Week 15 UI is running.
Close-up of the TFT and custom WROOM PCB connection
Close-up of the screen-to-board connection, useful for checking the real harness against board_pins.h.
Detailed wiring from the TFT screen into the WROOM display PCB
Detail of the screen wires landing on the WROOM display board.
Computer serial monitor on the left and opened Forest Spirit electronics with TFT UI on the right
Left: Serial monitor during the test. Right: the opened Forest Spirit assembly with the screen and WROOM display board visible.
Serial monitor detail showing sensor and I2C debug output
Serial detail: the XIAO reads local sensors and pushes the values to the WROOM screen over I²C.
Five UI pages on the TFT
MOOD page on the ILI9341 TFT — temperature emoji face driven by hub telemetry
MOOD page — emoji from DHT temperature (I²C from the XIAO).
PLANT INFO page on the ILI9341 TFT showing plant status and clock line
PLANT INFO page — plant status row and clock line from the XIAO.
SOIL page on the ILI9341 TFT showing soil sensor metrics
SOIL page — moisture, temperature, pH, EC, N/P/K bars (environment panel).
CONTROL page on the ILI9341 TFT with brightness volume sliders and mode buttons
CONTROL page — sliders and toggle buttons (touch in, status back over I²C).
AI chat page on the ILI9341 TFT showing dialogue with the forest spirit assistant
Chat page — bubbles for dialogue text sent from the host board.
Application swipe order

Five-page UI on the TFT: horizontal swipe through Emoji → Plant → Environment → Control → Chat on the WROOM setup (matches the HTML prototype order).

8) Source code (same as Downloads)

Duplicate of the Downloads block for readers who scroll to the end. §7d explains what the files do.

↓ Browse code/week15-individual/final/s3-hub/, wroom-display/, screen-ui.html

Display UI originals (wroom-display/src/):

↓ Download main.cpp · ↓ Download env_i2c_slave.cpp · ↓ Download i2c_env_link.h · ↓ Download board_pins.h · ↓ Download ft6336_touch.h · ↓ Download platformio.ini

Earlier Week 15 bring-up sketches (WROOM‑32D + ILI9341 display/touch split tests):

↓ Download main_test_screen.cpp — display tests + optional CTP paint loop

↓ Download main_test_touch.cpp — touch-centric demo with serial traces

↓ Download week15-smart-plant-companion-lcd-ui.html — first 320×240 HTML swipe prototype (§6)

PlatformIO projects pull Adafruit GFX / ILI9341 and DHT libraries via lib_deps. See final/README.md for pins and upload order.

9) Conclusion

The first combined Cursor sketch was a dead end: it assumed resistive SPI touch and put TFT reset on GPIO12, so I spent time on graphics when boot strapping was the real fault. Splitting display vs touch tests and moving reset off MTDI gave me a repeatable ILI9341 + FT6336 setup.

The finished UI redraws five HTML panels in C++ (§7d), sends pixels over SPI, reads touch on I²C, and pulls sensor/chat data on a second I²C port from my Week 8 XIAO. Sources are in wroom-display/; hero photos are at #week15-hero-ui-pages.

The host-link I²C path was the bridge between the screen UI and the rest of the plant companion. Adding Bailian push-to-talk speech (§11) turned the chat page from static mock-up into a live loop. Next I want the enclosure closed and the harness tested on the pot-mounted screen, not only temporary bring-up wiring.

10) What I took from the group toolchain comparison

The group section and the Chaihuo group page forced me to name when I actually open each tool instead of copying a feature matrix. Arduino IDE for the first “does the panel light up?” flash; VS Code + PlatformIO once wroom-display/ grew past one file; Cursor for the first HTML swipe draft I later ported into drawPage*() by hand.

Node-RED and MATLAB stayed on the comparison list even though I did not run them for the TFT UI. Thonny is still my MicroPython tool between hardware tests.

Group write-up: Chaihuo Week 15 group assignment.

11) Voice dialogue — Alibaba Bailian cloud path

The chat page in §7d is static in the hero photos. Forest Fairy also needs to answer in normal Mandarin sentences, not a fixed menu. I tried a XiaoZhi module first: mic and speaker were useful, but the bundled program fought our sensor bus and display stack. I kept the audio hardware and built our own cloud path instead.

Learning — Bailian multimodal dialog

Alibaba Bailian (百炼) exposes a multimodal-dialog WebSocket API on DashScope. Firmware opens wss://dashscope.aliyuncs.com/api-ws/v1/inference, sends 16 kHz mono PCM on push-to-talk, and receives 24 kHz TTS plus text events (transcript, dialog). The LLM model (e.g. Qwen) is chosen in the Bailian console when you create the app, not hard-coded in C++.

I set up a Forest Fairy app in the console, copied app_id and workspace_id into bailian_secrets.h, and ran pc_bailian_wss_test.py on the PC before flashing the board.

Alibaba Bailian apps overview in the cloud console
Bailian console: app list before creating the Forest Fairy dialog app.
Creating a new Bailian multimodal application
Creating the multimodal application.
Forest Fairy app created in Bailian
Forest Fairy app entry after creation.
Editing the Forest Fairy Bailian app settings
App editor: model and dialog settings.
API keys page in Alibaba Cloud
API keys for DashScope / Bailian.

Plan and hardware

  1. PC smoke test: verify keys, app id, and workspace with Python WSS client.
  2. Wire mic module and speaker to the WROOM display board (no third-party voice firmware).
  3. Port push-to-talk capture (asr_record.cpp) and WSS state machine (bailian_dialog_client.cpp).
  4. Print ASR and LLM strings on the AI chat TFT page; play TTS on I²S speaker.
  5. Archive sources under code/week17-individual/; merge into final upload tree later.
XiaoZhi voice module beside the Forest Fairy display test setup
XiaoZhi module test: decided to reuse mic/speaker only.
Microphone module wired for push-to-talk capture
Microphone module for I²S capture at 16 kHz.
Speaker module for TTS playback
Speaker module for 24 kHz TTS playback.

Source files

FileRole
asr_record.cppI²S mic capture, G1 button, stream PCM to WSS
bailian_dialog_client.cppDuplex WSS: Start → SendSpeech → StopSpeech → events
bailian_multimodal.cppThin API wrapper around the dialog client
local_chat.hASR + LLM reply strings for TFT chat page
pc_bailian_wss_test.pyPC smoke test before flash

Full build tree: code/week17-individual/ (week archive) and code/final-project-upload/wroom-upload/ (final upload). See README.md for PlatformIO targets and serial acceptance logs.

Demo video

This recording shows the full loop: I speak to the plant companion, Bailian returns ASR/LLM/TTS over WebSocket, the host board sends reply text over I²C, the TFT chat bubbles update, and the speaker plays the answer.

Voice dialogue demo: speech input, on-screen chat bubbles, and audio playback on the integrated Forest Fairy setup.

Latency is fine for a demo script but not for rapid back-and-forth. I will not ship scare alerts or mood stories from noisy sensor channels — spoken lines should trace to a sensor flag or an explicit unknown.

Group assignment

Guangzhou (Chaihuo) — we compared IDE options for interface programming. I wrote when I actually use each tool, not a copied feature matrix. Photos are shared group references.

1) Arduino IDE — first flash, first blink

I still open Arduino IDE when the only question is “does this board boot and drive one pin?” Library manager plus one sketch file beats setting up a new PlatformIO tree for a ten-minute sensor check.

Arduino IDE interface
Group reference image: Arduino IDE interface.

2) Thonny — MicroPython without ceremony

Thonny’s REPL and stop-on-error layout helped when I was toggling GPIO from MicroPython between hardware tests. I would not ship production firmware from it, but it is fine for classroom-scale scripts.

Thonny IDE interface
Group reference image: Thonny environment.

3) VS Code — daily editor for mixed work

Most of my Fab repo work lives in VS Code: PlatformIO builds, HTML assignment pages, and markdown notes in one tree. Cursor sits on top for the same reason when I want help drafting UI code.

Visual Studio Code interface
Group reference image: VS Code interface.

4) Node-RED — flow wiring for IoT glue

Our Chaihuo group page also listed Node-RED for visual flow-based IoT glue (sensors → APIs → dashboards). I did not run Node-RED on this week’s TFT UI path, but I would reach for it when the task is “connect three cloud services and a sensor feed” without writing a full firmware tree. The group comparison table lives on the Chaihuo Week 15 group page.

5) MATLAB/Simulink — model before wiring risky loops

I have not used Simulink on every week, but when a control loop or filter needs tuning before hardware smoke, modeling there is cheaper than burning a motor driver. Speed is not the point; fewer blind retries is.

MATLAB and Simulink
Group reference image: MATLAB/Simulink.

6) Cursor — AI on top of VS Code

I used Cursor to draft the first combined Fab Academy + button sketch and the 320×240 HTML swipe file, then rewrote the firmware by hand. It helps with multi-file edits, but I still check pin names against board_pins.h because the model once assumed resistive touch.

Cursor AI code editor interface
Group reference image: Cursor (VS Code foundation with integrated AI).

7) OpenAI Codex CLI

Codex CLI runs in the terminal. I use it when I am already in a shell (git status, PlatformIO upload, grep) and do not want to switch to an editor.

OpenAI Codex CLI terminal coding agent
Group reference image: Codex CLI in the terminal.

8) Claude Code

Our group also documented Claude Code in the terminal. I use it for refactors across wroom-display/ and s3-hub/, but I still flash and read Serial myself before calling the week done.

Claude Code terminal agent interface
Group reference image: Claude Code.

How I actually pick a toolchain

  • Arduino IDE for the first wire-up on unfamiliar boards.
  • VS Code (often with PlatformIO) once the project has more than one file or target.
  • Thonny for short MicroPython experiments.
  • Node-RED when the job is IoT service wiring, not on-device UI drawing.
  • MATLAB/Simulink when I need a simulated loop before touching power electronics.
  • Cursor, Codex CLI, or Claude Code when I want AI help drafting or refactoring — always followed by hardware tests.
Group toolchain summary (aligned with Chaihuo Week 15 group page)
ToolTypeAIWhen I reach for it
Arduino IDEDesktop IDENoFirst flash, one-file sensor checks
ThonnyDesktop IDENoMicroPython pin toggles between hardware tests
VS CodeEditor + extensionsVia extensionsDaily repo work, PlatformIO builds
Node-REDVisual flowsNoIoT glue; not used on this TFT week
MATLAB + SimulinkSimulationNoControl loops before risky hardware
CursorAI IDEYesHTML UI draft, early combined sketch
Codex CLITerminal agentYesShell-first edits and uploads
Claude CodeTerminal agentYesMulti-folder refactors with git context

Source

Group template and media source: Week 15 — Group Assignment: Interface and Application Programming.