This document serves as a comprehensive guide to understanding the entire Raspberry Pi Timeline application. It details how the disparate technologies (React, Rust, SQLite, Axum, Tauri, and Vite) all work together to deliver a self-contained, fully offline Kiosk experience.
At its core, this project is a Tauri Desktop Application that behaves like two completely different softwares stitched together:
- The Display Interface: A visual screen built in React that runs like a normal desktop app (compiled natively via Tauri) directly on the Raspberry Pi display.
- The Hidden Kiosk Server: A HTTP Web Server built in Rust that launches silently in the background on port
8080, answering incoming requests from laptops and phones connected to your home Wi-Fi.
Both of these systems share a single brain: a local SQLite Database File (timeline.db).
Tip
This architecture completely eliminates the need for cloud database subscriptions (like Supabase or Firebase) while providing the same user experience of uploading memories from your personal phone or laptop securely.
When you boot up this application, the Rust backend immediately creates a tiny database file directly next to the running executable called timeline.db. It sets up an events table with robust columns (id, event_date, title, description, image_url). This file is the permanent memory of the digital photo frame.
Your codebase in the src/ directory represents the visual frontend.
- When Tauri boots up, it compiles this React code using Vite into static HTML and JavaScript.
- Tauri then injects these static files into a native, high-performance webview window (essentially a minimalist Chrome browser stripped of borders and URLs).
- Auto-Polling: Inside
src/routes/index.tsx, the interface is instructed to usesetIntervalto "poll" the Rust backend every 5 seconds. It safely executes a Tauri command calledinvoke("get_events")asking: "Hey Rust, are there any new memories in the SQLite database?" - If yes, the
TimelineItemcomponents cascade down the screen to render the chronological history.
Your codebase in the src-tauri/ directory represents the hidden backend.
- We imported an incredibly fast web framework called Axum. When Tauri launches the desktop display window, we force Axum to simultaneously launch onto your Raspberry Pi's
8080port. - This web server has two endpoints:
GET /-> When your phone connects to the Raspberry Pi over Wi-Fi (http://192.168.1.X:8080), Axum serves the Tailwind-styledsrc-tauri/src/uploader.htmlfile right to your browser screen.POST /api/events-> When you hit "Send to Display!" on that HTML form, the form shoots a JSON payload to this endpoint. Axum intercepts it, opens up thetimeline.dbSQLite file securely, and inserts the data forever.
Because the React app is actively polling that same database every 5 seconds, it visually updates almost instantly!
Here is exactly what every critical file does in this system:
package.json: Tracks the JavaScript dependencies for the React frontend (like@tanstack/react-router).src-tauri/Cargo.toml: Tracks the Rust dependencies for the backend (likerusqlitefor the database andaxumfor the local Web Server).src-tauri/tauri.conf.json: The orchestrator. It tells Tauri where your Vite build is located, what your application icon is, and configuration details for compilation.
src/routes/index.tsx: The main hub of the visual display. This fetches theTimelineEventrecords via Tauri IPC and renders them down the screen.src/components/TimelineItem.tsx: The reusable React component that dictates exactly how a single historical memory looks (font sizes, image rendering, margins, layout).
src-tauri/src/lib.rs: The backbone of the entire application. It contains the Rust instructions to:- Hook into the
timeline.dbfile. - Define Tauri commands (
get_events) so the React display interface can securely query information. - Initialize the Axum web server on a background asynchronous
Tokiothread, allowing it to process HTTP requests simultaneously completely insulated from the desktop interface thread.
- Hook into the
src-tauri/src/uploader.html: The entire frontend of your Uploader Web Portal. I bundled it straight into the Rust code viainclude_str!()so it compiles securely into the binary executable. Nothing floating around!
Warning
This is arguably the most critically important design pattern in the application.
SQLite databases are strictly single-file architectures. If two different programs try to write to a SQLite file at the exact same millisecond, the entire database crashes and corrupts.
Because we have two things running simultaneously (The React UI polling data via Tauri, and the Axum Server accepting new inputs via your phone), they could easily crash into each other.
In src-tauri/src/lib.rs, the SQLite connection object is wrapped in an Arc<Mutex<Connection>>.
Mutex(Mutual Exclusion): This serves as a lock on a door. Whether Tauri tries to fetch events, or Axum tries to write an event, they must "lock" the Mutex. The other system physically has to wait in line until the transaction is safe and 100% complete.Arc(Atomic Reference Count): This allows us to give one master key to the Tauri Display state, and one master key to the Axum Server thread, allowing them to both own the Mutex securely.
The Raspberry Pi uses a fundamentally different CPU architecture than a Mac (ARM64 Linux vs x86_64 macOS). You cannot compile the Tauri app on your Mac and drag it onto the Pi — the binaries are completely incompatible.
The old approach would be to install Rust, Node.js, and all build tools directly on the Pi and compile everything there. While that works, it takes 20-30 minutes of compilation time on the Pi's slower hardware, and needs to be repeated every time you update the app.
Instead, we use GitHub Actions to cross-compile the app for the Pi in the cloud — the Pi only ever needs to download and install a ready-made .deb file.
This YAML file is a set of instructions that GitHub's cloud computers follow automatically whenever you trigger a new release. Here is what each step does:
- Checkout code — Downloads the latest version of your repository onto the cloud server.
- Install Linux system dependencies — Installs the native Linux libraries (like
libwebkit2gtk) that Tauri needs to render its window on Linux. It also installsgcc-aarch64-linux-gnu, a special compiler that can produce ARM64 code while running on an x86 server. - Install Rust — Installs the Rust compiler and adds the
aarch64-unknown-linux-gnucompilation target, which tells Rust to produce ARM64 Linux binaries. - Cache dependencies — Saves previously compiled Rust and npm packages to make future builds significantly faster (typically cuts build time from 15 minutes down to 3-5 minutes after the first run).
- Install Node.js + pnpm — Installs the JavaScript tools needed to compile the React frontend.
- Install JS dependencies — Runs
pnpm installto fetch all frontend packages. - Build with
tauri-action— Tauri's official GitHub Action compiles both the React frontend (via Vite) and the Rust backend (cross-compiled for ARM64). It then bundles them into a.debinstaller file and attaches it to a new GitHub Release automatically.
Note
The CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER environment variable is the critical piece that makes cross-compilation work. It tells the Rust linker to use the ARM64 cross-compiler (aarch64-linux-gnu-gcc) instead of the default x86 linker. Without this, the build would produce a binary that runs on the cloud server but crashes on the Pi.
The workflow fires on two conditions:
- A version tag push — The primary trigger. When you push a tag starting with
v(likev1.0,v1.1,v2.0), the workflow starts automatically. - Manual dispatch — You can also click "Run workflow" from the Actions tab in your GitHub repository at any time without making a new commit.
Workflow for releasing a new version of the app from your Mac:
# 1. Commit all your changes
git add .
git commit -m "Add new memories feature"
# 2. Create a version tag
git tag v1.0
# 3. Push both the code and the tag
git push && git push --tagsAfter ~10-15 minutes, a new entry will appear on your GitHub repository's "Releases" page (the bookmark icon on the right sidebar) with the .deb file attached.
Once a release exists on GitHub, the Pi only needs two commands — ever. No Rust, no Node.js, no building anything locally.
# 1. Download the installer from your GitHub Releases page
# (replace the URL with the actual link from the Releases page)
wget https://github.com/YOUR_USERNAME/TestingTauri/releases/download/v1.0/timeline-app_0.1.0_arm64.deb
# 2. Install it like any standard Linux application
sudo dpkg -i timeline-app_0.1.0_arm64.debTo launch the kiosk display:
timeline-appTo update to a newer version in the future, simply wget the new .deb file and run dpkg -i again — it automatically overwrites the old installation. The timeline.db database file (stored safely in ~/.local/share/timeline-app/) is never touched during an update, so all your memories are preserved.
Edit code on Mac
│
▼
git push && git push --tags
│
▼
GitHub Actions cloud server builds ARM64 .deb (~10-15 min)
│
▼
.deb file appears on GitHub Releases page
│
▼
Pi: wget + dpkg -i (30 seconds)
│
▼
timeline-app is live on the Pi display