Monorepo for Aesthetic.Computer
aesthetic.computer
1# Git Commit Status Indicator for Prompt Curtain
2
3## Overview
4Add a visual indicator to the prompt.mjs curtain UI showing whether the deployed site is on the latest git commit or behind, with the commit hash displayed.
5
6## Visual Design
7- **Location**: Below "X HANDLES SET" text on the login curtain
8- **Font**: MatrixChunky8 (same as handles counter)
9- **Colors**:
10 - 🟢 Green (`[0, 255, 0]`) = Site is up-to-date with latest commit
11 - 🟠 Orange (`[255, 165, 0]`) = Site is behind (shows how many commits)
12- **Format Examples**:
13 - `✓ 37da247` (current)
14 - `↑ 2 behind (37da247)` (behind by 2 commits)
15
16---
17
18## Implementation Steps
19
20### 1. Create Build-Time Version File
21**File**: `system/public/version.json` (generated at build time)
22
23Add to `netlify.toml` build command:
24```toml
25command = "rm -f public/index.html && echo '{\"commit\":\"'$COMMIT_REF'\",\"timestamp\":\"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'\"}' > public/version.json"
26```
27
28This creates a file like:
29```json
30{"commit":"37da2475abc123...","timestamp":"2026-01-11T22:34:06Z"}
31```
32
33### 2. Create Netlify Function: `/api/version`
34**File**: `system/netlify/functions/version.mjs`
35
36```javascript
37// Fetches latest commit from GitHub and compares to deployed version
38export default async (request) => {
39 const deployedCommit = process.env.COMMIT_REF || "unknown";
40
41 try {
42 // Fetch latest commit from GitHub (public, no auth needed)
43 const res = await fetch(
44 "https://api.github.com/repos/whistlegraph/aesthetic-computer/commits?per_page=50",
45 { headers: { "User-Agent": "aesthetic-computer" } }
46 );
47 const commits = await res.json();
48 const latestCommit = commits[0]?.sha;
49
50 // Find how many commits behind
51 let behindBy = 0;
52 if (deployedCommit !== "unknown" && latestCommit) {
53 const idx = commits.findIndex(c => c.sha.startsWith(deployedCommit.slice(0, 7)));
54 behindBy = idx === -1 ? 50 : idx; // -1 means very old
55 }
56
57 const status = behindBy === 0 ? "current" : "behind";
58
59 return Response.json({
60 deployed: deployedCommit.slice(0, 7),
61 latest: latestCommit?.slice(0, 7),
62 status,
63 behindBy,
64 timestamp: new Date().toISOString()
65 });
66 } catch (e) {
67 return Response.json({
68 deployed: deployedCommit.slice(0, 7),
69 status: "unknown",
70 error: e.message
71 });
72 }
73};
74
75export const config = { path: "/api/version" };
76```
77
78### 3. Add Redirect in `netlify.toml`
79```toml
80[[redirects]]
81from = "/api/version"
82to = "/.netlify/functions/version"
83status = 200
84```
85
86### 4. Update `prompt.mjs`
87
88#### A. Add State Variables (~line 230)
89```javascript
90let versionInfo = null; // { deployed, latest, status, behindBy }
91```
92
93#### B. Fetch in `boot()` (~line 640, after handles fetch)
94```javascript
95// Fetch commit/version status
96const fetchVersion = async () => {
97 try {
98 const res = await fetch("/api/version");
99 versionInfo = await res.json();
100 needsPaint();
101 } catch (e) {
102 console.warn("Could not fetch version info:", e);
103 }
104};
105fetchVersion();
106// Refresh every 5 minutes
107setInterval(fetchVersion, 5 * 60 * 1000);
108```
109
110#### C. Render in `paint()` (~line 6081, after handles text)
111```javascript
112// Git commit status indicator
113if (versionInfo && screen.height >= 120) {
114 const versionY = handlesY + 12;
115 let versionText, versionColor;
116
117 if (versionInfo.status === "current") {
118 versionColor = [0, 255, 0]; // Green
119 versionText = `✓ ${versionInfo.deployed}`;
120 } else if (versionInfo.status === "behind") {
121 versionColor = [255, 165, 0]; // Orange
122 versionText = `↑ ${versionInfo.behindBy} behind (${versionInfo.deployed})`;
123 } else {
124 versionColor = [128, 128, 128]; // Gray for unknown
125 versionText = `? ${versionInfo.deployed || "unknown"}`;
126 }
127
128 ink(versionColor).write(
129 versionText,
130 { center: "x", y: versionY },
131 undefined, undefined, false, "MatrixChunky8"
132 );
133}
134```
135
136---
137
138## File Changes Summary
139
140| File | Change |
141|------|--------|
142| `system/netlify.toml` | Update build command to generate version.json; add redirect |
143| `system/netlify/functions/version.mjs` | New function (create) |
144| `system/public/aesthetic.computer/disks/prompt.mjs` | Add state var, boot fetch, paint render |
145
146---
147
148## Technical Notes
149
150### GitHub API
151- **Endpoint**: `GET https://api.github.com/repos/whistlegraph/aesthetic-computer/commits`
152- **Rate limit**: 60 requests/hour unauthenticated (sufficient with 5-min polling + server-side caching)
153- **No auth required** for public repos
154
155### Netlify Environment Variables
156- `COMMIT_REF` - Full SHA of the commit being deployed (available at build/function time)
157- `BUILD_ID` - Unique build identifier
158
159### Caching Strategy
160The Netlify function can add cache headers to reduce GitHub API calls:
161```javascript
162return new Response(JSON.stringify(data), {
163 headers: {
164 "Content-Type": "application/json",
165 "Cache-Control": "public, max-age=60" // Cache for 1 minute
166 }
167});
168```
169
170---
171
172## Future Enhancements
173- Click on commit hash to open GitHub commit page
174- Show commit message preview on hover
175- Add "Update available" notification badge
176- Track deploy history / show last N deploys