Author SHA1 Message Date
hermes 2d57173e31 fix: CodeGit looked like an empty black box - hardcoded panel height + cache
Reported as "why can't i see anything from codegit its just a black box".

NOT a codegit bug. Verified first, exhaustively, through the authenticated API:
  overview  200    940 bytes
  log       200  79596 bytes   (full commit history)
  transfer  200   1460 bytes
  config    200   1174 bytes
no JS errors in the console; every DOM helper codegit calls exists.

ROOT CAUSE 1 - a generic CSS rule hardcodes a height
  theme/modules/common/_panel.scss -> theme/main.min.css:
    panel { ... height: 500px; ... }
  That forces EVERY modal panel to a fixed 500px box. Measured live:
    #dialog            404px  (clamped by max-height:70vh + overflow-y:auto)
    #codegit panel     500px, while its content is only 119px
    toolbar ("Branch") top 620 vs dialog bottom 490 -> OUTSIDE the modal
  So ~380px of the dialog was empty dead space and the Branch line rendered below the
  modal, invisible. Fixed to height:auto with max-height:58vh (long content still scrolls):
    before: panel 500 -> toolbar top 620, dialog bottom 490 (outside)
    after:  panel 136 -> toolbar top 256, dialog bottom 279 (inside)
  _panel.scss updated too so the source does not contradict the shipped CSS.

ROOT CAUSE 2 - the fix was invisible because of the browser cache
  Atheos serves theme/main.min.css and the generated bundles from stable URLs with no cache
  headers, so browsers legitimately reuse their cached copy - which is why the first attempt
  appeared to do nothing. nginx now sends Cache-Control: no-cache, must-revalidate for
  *.css/*.js: caching still works (cheap 304s) but changes are always picked up.

Adds
  scripts/fix-panel-layout.sh   idempotent re-apply for both fixes (lost on update-atheos)
  docs/known-issues.md §4, §5   both findings, with the measurements

Tests (44 passed, 1 failed - the failure is branch protection, a genuine open item)
  + panel must not be fixed at 500px
  + theme CSS must send Cache-Control: no-cache
2026-09-29 16:38:48 +10:00
hermes c65900638e Merge pull request 'fix: rDelete fatals on musl (GLOB_BRACE) - broke ALL deletion and plugin removal' (#4) from fix/glob-brace-musl into master 2026-09-29 16:28:57 +10:00
4 changed files with 194 additions and 17 deletions
+79 -7
View File
@@ -224,7 +224,79 @@ Worth doing if the dialog starts 500-ing again after editing a macro in the UI.
--- ---
## 4. Checking for plugin problems in future ## 4. 🟠 The CodeGit panel looked like an empty black box
**Symptom:** the CodeGit dialog appeared as a mostly-empty black rectangle. Nothing was *broken* —
it was unreadable, which reads as "broken".
**This was NOT a codegit bug.** Verified exhaustively first:
```
all four panels return real content through the authenticated API:
overview 200 940 bytes
log 200 79596 bytes (full commit history)
transfer 200 1460 bytes
config 200 1174 bytes
no JS errors in the browser console; all DOM helpers (replaceClass etc.) exist
```
**Root cause — the generic `panel` rule hardcodes a height.** `theme/modules/common/_panel.scss`
(after minification, in `theme/main.min.css`):
```css
panel { padding: 5px; width: calc(100% - 200px); overflow: auto; overflow-y: scroll;
height: 500px; position: relative; border-left: var(--borderLight); }
```
Measured live, that produces:
| Element | Height | Problem |
|---|---|---|
| `#dialog` | 404px | clamped by `max-height: 70vh` + `overflow-y: auto` |
| `#codegit panel` | 500px | **forced**, while its content is 119px |
| `toolbar` ("Branch: …") | top **620** vs dialog bottom **490** | **pushed OUTSIDE the modal** |
So ~380px of the dialog was **empty dead space**, and anything after the panel — including the
Branch line — rendered *below* the modal, invisible. A generic `panel { height: 500px }` is exactly
the kind of rule that looks like a void.
**Fix applied:**
```css
panel { ... height: auto; max-height: 58vh; overflow-y: auto; ... }
```
Content-sized, capped so long content (the 79KB log) still scrolls. Verified live:
```
before: panel 500px → toolbar top 620, dialog bottom 490 (outside)
after: panel 136px → toolbar top 256, dialog bottom 279 (inside)
```
`theme/modules/common/_panel.scss` is updated too, so the source does not contradict the shipped CSS
(there is no Sass compiler on the guest, so `main.min.css` is edited directly).
---
## 5. 🟠 CSS/JS changes were invisible behind the browser cache
**Symptom:** a fix to the theme did not appear until a hard refresh — so the panel fix above looked
like it had not worked at all.
**Root cause:** Atheos serves `theme/main.min.css` and the generated `public/*.min.js|css` bundles
from **stable URLs with no cache headers**, so nginx adds only `Last-Modified`/`ETag` and browsers
legitimately reuse their cached copy.
**Fix:** nginx now sends `Cache-Control: no-cache, must-revalidate` for `*.css`/`*.js`. Note this does
**not** disable caching — it forces **revalidation**, so normal loads still get a cheap 304 while any
change is guaranteed to be picked up on the next load.
**If you ever see a stale UI again:** force a reload (Ctrl/Cmd-Shift-R). That is the first thing to try
before assuming a fix failed.
---
## 6. Checking for plugin problems in future
The plugin's JS is bundled into `public/plugins.min.js` (built by `classes/sourcemanager.php`), and The plugin's JS is bundled into `public/plugins.min.js` (built by `classes/sourcemanager.php`), and
its rightbar button is rendered by `components/sidebars/sb-right.php` from `plugin.json`. Both were its rightbar button is rendered by `components/sidebars/sb-right.php` from `plugin.json`. Both were
@@ -258,17 +330,17 @@ verify them before trusting it.
--- ---
## 5. Follow-ups not done (recorded so they are not forgotten) ## 7. Follow-ups not done (recorded so they are not forgotten)
These came out of review. They are **operator decisions**, not agent ones — several change what the These came out of review. They are **operator decisions**, not agent ones — several change what the
editor can do. editor can do.
### 5.1 Remove the Terminal plugin — ✅ DONE ### 7.1 Remove the Terminal plugin — ✅ DONE
Removed through the market's own code path once §1 was fixed (see §2). A browser shell was the largest Removed through the market's own code path once §1 was fixed (see §2). A browser shell was the largest
attack surface in the container and macros already cover command execution. attack surface in the container and macros already cover command execution.
### 5.2 ❌ `disable_functions` cannot be "on by default" here — measured ### 7.2 ❌ `disable_functions` cannot be "on by default" here — measured
Review asked for `php.ini` to disable `exec`, `shell_exec`, `passthru`, `system` by default. Review asked for `php.ini` to disable `exec`, `shell_exec`, `passthru`, `system` by default.
**This would break the editor's core function.** Evidence: **This would break the editor's core function.** Evidence:
@@ -285,18 +357,18 @@ turns the editor into a text editor that cannot commit. So it stays **a delibera
you would also lose macros and git integration, which is why S-7 already says *"test, do not you would also lose macros and git integration, which is why S-7 already says *"test, do not
blind-apply"*. blind-apply"*.
### 5.3 Rotate the terminal password automatically ### 7.3 Rotate the terminal password automatically
If the plugin is kept, `provision.sh` should generate a random password rather than leaving the If the plugin is kept, `provision.sh` should generate a random password rather than leaving the
upstream default. Not implemented: it would hardcode a secret into a repo file, which is the same upstream default. Not implemented: it would hardcode a secret into a repo file, which is the same
class of problem this file already documents. class of problem this file already documents.
### 5.4 Deploy key could be read-only ### 7.4 Deploy key could be read-only
Review suggested making the deploy key read-only if writes are unnecessary. **They are necessary** — Review suggested making the deploy key read-only if writes are unnecessary. **They are necessary** —
that is the whole point of the writer-branch workflow (macro 2 pushes). Not applicable. that is the whole point of the writer-branch workflow (macro 2 pushes). Not applicable.
### 5.5 The real upstream fix for §3 ### 7.5 The real upstream fix for §3
`class.macro.php::save()` should write `[]` instead of `"N/A"` for folder macros. See §3 — the fix is `class.macro.php::save()` should write `[]` instead of `"N/A"` for folder macros. See §3 — the fix is
currently applied to the data, so it survives updates, at the cost of being reversible from the UI. currently applied to the data, so it survives updates, at the cost of being reversible from the UI.
+14 -5
View File
@@ -7,13 +7,12 @@ server {
root /var/www/atheos; root /var/www/atheos;
index index.php index.html; index index.php index.html;
# --- logging ---------------------------------------------------------
access_log /var/log/nginx/atheos.access.log; access_log /var/log/nginx/atheos.access.log;
error_log /var/log/nginx/atheos.error.log; error_log /var/log/nginx/atheos.error.log;
# --- PHP ----------------------------------------------------------------- # --- PHP: the socket path here MUST byte-match the pool's `listen =`
# The socket path here MUST byte-match the pool's `listen =` in # in /etc/php83/php-fpm.d/www.conf (interface I-1 -> else bare 502)
# /etc/php83/php-fpm.d/www.conf
# (interface I-1 — a mismatch yields a bare 502 with no other diagnostic).
location ~ \.php$ { location ~ \.php$ {
try_files $uri =404; try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_split_path_info ^(.+\.php)(/.+)$;
@@ -27,7 +26,8 @@ server {
try_files $uri $uri/ =404; try_files $uri $uri/ =404;
} }
# --- hardening ----------------------------------------------------------- # --- hardening (free, no downside) -----------------------------------
# never serve the VCS directory, the app's private data, or dotfiles
location ~ /\.git { deny all; } location ~ /\.git { deny all; }
location ~ /\.ssh { deny all; } location ~ /\.ssh { deny all; }
location ~ /\. { deny all; } location ~ /\. { deny all; }
@@ -40,4 +40,13 @@ server {
add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always;
client_max_body_size 64m; client_max_body_size 64m;
# Atheos serves theme/main.min.css and the generated *.min.js/css bundles from STABLE URLs
# with no cache headers. Without this, a CSS/JS change stays invisible behind a cached copy
# until the user hard-refreshes. "no-cache" permits caching but forces revalidation, so a
# 304 keeps normal loads fast while guaranteeing updates are picked up on the next load.
location ~* \.(css|js)$ {
add_header Cache-Control "no-cache, must-revalidate";
try_files $uri =404;
}
} }
+21 -1
View File
@@ -106,11 +106,31 @@ for d in mysqld mariadbd postgres redis-server; do
if command -v "$d" >/dev/null 2>&1; then bad "$d present - unexpected"; else ok "$d absent (correct)"; fi if command -v "$d" >/dev/null 2>&1; then bad "$d present - unexpected"; else ok "$d absent (correct)"; fi
done done
echo
echo "=============================================================="
echo "REGRESSION: panel has no hardcoded height (empty-modal bug)"
echo "=============================================================="
# panel { height:500px } forced every modal panel to a fixed box: short content left a large
# empty black region and anything after the panel (codegit's Branch toolbar) was pushed outside
# the modal. docs/known-issues.md §4. Fails loudly if lost to an update.
PNL=$(grep -oE '(^|[;}])panel\{[^}]*\}' /var/www/atheos/theme/main.min.css 2>/dev/null | head -1)
if [ -z "$PNL" ]; then
bad "could not read the panel rule from theme/main.min.css"
elif echo "$PNL" | grep -q 'height:500px'; then
bad "panel is fixed at 500px - re-run scripts/fix-panel-layout.sh"
else
ok "panel is content-sized ($(echo "$PNL" | grep -oE 'height:[^;]*' | head -1))"
fi
# and the CSS/JS cache header must be present, or fixes stay invisible
if wget -qO /dev/null --server-response --header='Host: 127.0.0.1' http://127.0.0.1/theme/main.min.css 2>&1 | grep -qi 'Cache-Control.*no-cache'; then
ok "theme CSS sends Cache-Control: no-cache (changes are picked up)"
else
bad "theme CSS has no Cache-Control - a CSS fix can stay hidden behind the browser cache"
fi
echo echo
echo "==============================================================" echo "=============================================================="
echo "REGRESSION: rDelete works (GLOB_BRACE is glibc-only; musl lacks it)" echo "REGRESSION: rDelete works (GLOB_BRACE is glibc-only; musl lacks it)"
echo "==============================================================" echo "=============================================================="
# traits/helpers.php::rDelete() used GLOB_BRACE, which is undefined under musl -> PHP 8 fatal.
# That broke deleting ANY file/folder in the editor AND removing a market plugin (the UI # That broke deleting ANY file/folder in the editor AND removing a market plugin (the UI
# would spin forever because the fatal killed the response before Common::send()). # would spin forever because the fatal killed the response before Common::send()).
# docs/known-issues.md §1. This test fails loudly if the patch is lost (e.g. by an update). # docs/known-issues.md §1. This test fails loudly if the patch is lost (e.g. by an update).
+76
View File
@@ -0,0 +1,76 @@
#!/bin/sh
# Re-apply the UI/layout fixes to a rebuilt or updated Atheos tree.
#
# Why this exists: Atheos has no releases and `scripts/update-atheos.sh` replaces the whole
# application directory, so any local patch is lost on update. These are the two that were
# needed to make the editor usable. Both are IDEMPOTENT - run it as often as you like.
#
# 1. panel { height: 500px } -> height:auto; max-height:58vh
# Every modal panel was forced to a fixed 500px box, so dialogs with short content showed
# a large empty black region and anything after the panel (codegit's "Branch:" toolbar)
# was pushed OUTSIDE the modal. See docs/known-issues.md §4.
#
# 2. nginx: Cache-Control: no-cache for *.css/*.js
# Theme/bundle changes were invisible behind the browser cache. §5.
#
# Usage: sh scripts/fix-panel-layout.sh (run ON the guest, as root)
set -e
ATH=/var/www/atheos
CSS=$ATH/theme/main.min.css
SCSS=$ATH/theme/modules/common/_panel.scss
NGX=/etc/nginx/http.d/atheos.conf
echo "=== 1. panel layout ==="
OLD='height:500px;position:relative;border-left:var(--borderLight)'
NEW='height:auto;max-height:58vh;position:relative;border-left:var(--borderLight)'
if [ ! -f "$CSS" ]; then
echo " !! $CSS not found - is Atheos installed?"
elif grep -qF "$NEW" "$CSS"; then
echo " already applied"
elif grep -qF "$OLD" "$CSS"; then
[ -f "$CSS.orig" ] || cp -a "$CSS" "$CSS.orig"
php83 -r '$f=$argv[1];$o=$argv[2];$n=$argv[3];$s=file_get_contents($f);
if(strpos($s,$o)===false){exit(1);} file_put_contents($f,str_replace($o,$n,$s));' "$CSS" "$OLD" "$NEW"
echo " applied to main.min.css"
else
echo " !! expected panel rule not found - inspect manually"
echo " (upstream may have changed it; diff against $CSS.orig)"
fi
# keep the SCSS source consistent so the shipped CSS is not contradicted by the source
if [ -f "$SCSS" ] && grep -q 'height: 500px;' "$SCSS"; then
[ -f "$SCSS.orig" ] || cp -a "$SCSS" "$SCSS.orig"
php83 -r '$f=$argv[1];$s=file_get_contents($f);
$s=str_replace("height: 500px;","height: auto;\n\tmax-height: 58vh;",$s);
file_put_contents($f,$s);' "$SCSS"
echo " synchronised _panel.scss"
fi
echo "=== 2. nginx cache headers ==="
if [ ! -f "$NGX" ]; then
echo " !! $NGX not found"
elif grep -q 'must-revalidate' "$NGX"; then
echo " already applied"
else
[ -f "$NGX.orig" ] || cp -a "$NGX" "$NGX.orig"
cat > /tmp/.cacheblock <<'EOF'
# Atheos serves theme/main.min.css and the generated *.min.js/css bundles from STABLE URLs
# with no cache headers, so a CSS/JS fix stays invisible behind the browser cache until a
# hard refresh. "no-cache" permits caching but forces revalidation: normal loads still get a
# cheap 304, and a change is guaranteed to be picked up on the next load.
location ~* \.(css|js)$ {
add_header Cache-Control "no-cache, must-revalidate";
try_files $uri =404;
}
EOF
php83 -r '$f=$argv[1];$b=$argv[2];$s=file_get_contents($f);
$p=strrpos($s,"}"); if($p===false){exit(1);}
file_put_contents($f,substr($s,0,$p).file_get_contents($b).substr($s,$p));' "$NGX" /tmp/.cacheblock
rm -f /tmp/.cacheblock
nginx -t >/dev/null 2>&1 && rc-service nginx reload >/dev/null 2>&1 && echo " applied + nginx reloaded" \
|| echo " !! nginx -t failed - restore $NGX.orig"
fi
echo "=== done ==="