Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/open_apps/apps/assets/vendor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Vendored frontend assets

Third-party JavaScript and CSS, committed rather than fetched at page load.

## Why these are checked in

`fast_app()` / `FastHTML()` load htmx, Pico and three helper scripts from
`cdn.jsdelivr.net` by default. On a host with no outbound network — which is
what the SLURM eval nodes are — none of it arrives, and the failure is silent
and severe:

* **htmx missing** means every `hx-get` / `hx-post` / `hx-put` in every app is
inert. A checkbox still *appears* to toggle, because that is native browser
behaviour, but no request is sent and no server state changes. An agent
clicks the right element, the screenshot shows the click landed, and the task
scores zero.
* **Pico missing** means every page renders unstyled. For a vision agent scored
on screenshots, that changes the observation itself.

Neither failure raises an error anywhere. Serving these locally is what makes
the environment behave the same offline as it does on a laptop.

There is precedent: `../js/jquery.min.js` and `../css/fontawesome-all.min.css`
are already vendored the same way.

## Contents

| File | Version | Upstream | License |
|------|---------|----------|---------|
| `htmx-2.0.4.min.js` | 2.0.4 | https://github.com/bigskysoftware/htmx | 0BSD |
| `pico-2.1.1.min.css` | 2.1.1 | https://github.com/picocss/pico | MIT |

Both are permissive and require no notice retention beyond the copyright
headers already inside the files. Pico's header is intact at the top of the
CSS; htmx's minified bundle carries no header, so its copyright is recorded
here: *Copyright (c) 2020, Big Sky Software — Zero-Clause BSD.*

Versions are pinned in the filename on purpose. FastHTML's default pulled
`@picocss/pico@latest`, so the styling of an eval run depended on when it ran.

## Updating

```sh
V=2.0.5
curl -fsSL "https://cdn.jsdelivr.net/npm/htmx.org@${V}/dist/htmx.min.js" \
-o "src/open_apps/apps/assets/vendor/htmx-${V}.min.js"
```

Then update the constant in `src/open_apps/frontend.py`, delete the old file,
and update the table above. `tests/test_no_egress.py` will fail if a page ends
up referencing an origin that isn't explicitly allowed.

## What is deliberately *not* vendored

`fasthtml.js`, `surreal.js` and `css-scope-inline` are the remaining FastHTML
defaults. Nothing in this repo uses them, so they are switched off rather than
copied in — every vendored file is one more thing to keep patched.
1 change: 1 addition & 0 deletions src/open_apps/apps/assets/vendor/htmx-2.0.4.min.js

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/open_apps/apps/assets/vendor/pico-2.1.1.min.css

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/open_apps/apps/calendar_app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Ul, Li, Hr, Article, Button, RedirectResponse, Container, MarkdownJS,
HighlightJS, database, dataclass)
from datetime import datetime, timedelta
from src.open_apps.frontend import local_hdrs
import calendar
import os
import logging
Expand Down Expand Up @@ -185,8 +186,9 @@ def generate_styles_from_config(config):


app, rt = fast_app(
pico=True,
default_hdrs=False,
hdrs=(
*local_hdrs(),
MarkdownJS(),
HighlightJS(langs=["python", "javascript", "html", "css"]),
Script(src="https://unpkg.com/@phosphor-icons/web"),
Expand Down
9 changes: 6 additions & 3 deletions src/open_apps/apps/codeeditor_app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
import json
from starlette.responses import Response
from src.open_apps.apps.start_page.helper import create_logo_header
from src.open_apps.frontend import local_hdrs

# Global variables
_base_hdrs_no_highlight = (
picolink,
# Pico + htmx from apps/assets/vendor, not jsdelivr (see frontend.py).
*local_hdrs(),
Script(src="https://cdn.tailwindcss.com"),
Link(
rel="stylesheet",
Expand All @@ -33,7 +35,7 @@
logo_title_container = None

# Initialize app with default headers
app = FastHTML(hdrs=_base_hdrs, cls="p-4")
app = FastHTML(hdrs=[*local_hdrs(), *_base_hdrs], cls="p-4", default_hdrs=False)

import yaml
import os
Expand Down Expand Up @@ -91,7 +93,8 @@ def set_environment(config):
update_db_from_hydra(config)
print(f"- Code editor filesystem created under {current_dir}")
_base_hdrs_with_highlight = (
picolink,
# Pico + htmx from apps/assets/vendor, not jsdelivr (see frontend.py).
*local_hdrs(),
Script(src="https://cdn.tailwindcss.com"),
Link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/daisyui@4.11.1/dist/full.min.css"),
Link(rel="stylesheet", href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css"),
Expand Down
10 changes: 7 additions & 3 deletions src/open_apps/apps/messenger_app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import ast
import json
from src.open_apps.apps.start_page.helper import create_logo_header
from open_apps.frontend import local_hdrs


@dataclass
Expand Down Expand Up @@ -253,8 +254,11 @@ class Messages:
""")
)
_base_hdrs = (
picolink,
Script(src="https://unpkg.com/htmx.org@1.9.10"), # Add this line if not present
# Pico and htmx served from apps/assets/vendor rather than a CDN. This app
# previously loaded picolink (Pico from jsdelivr) plus htmx 1.9.10 from
# unpkg, on top of the htmx 2.0.4 FastHTML injects by default -- two htmx
# versions racing on a page, both of which vanish on an offline host.
*local_hdrs(),
Script(src="https://cdn.tailwindcss.com"),
Link(
rel="stylesheet",
Expand All @@ -266,7 +270,7 @@ class Messages:
),
_base_chat_script,
)
app = FastHTML(hdrs=_base_hdrs, cls="p-4 max-w-lg mx-auto")
app = FastHTML(hdrs=_base_hdrs, cls="p-4 max-w-lg mx-auto", default_hdrs=False)


def set_environment(config):
Expand Down
10 changes: 9 additions & 1 deletion src/open_apps/apps/start_page/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import subprocess
from pathlib import Path

from open_apps.frontend import local_hdrs

# src/proficiency_playground/playground_server
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

Expand Down Expand Up @@ -598,7 +600,13 @@ def get_app(hdrs=None, *args, **kwargs):
href="/assets/css/main.css",
)
)
app = FastHTML(hdrs=hdrs, *args, **kwargs)
# This is the app that actually serves every route -- the other apps' routes
# are mounted onto it -- so its headers are what the browser sees. htmx and
# Pico come from apps/assets/vendor via the static route below rather than
# from jsdelivr; default_hdrs=False is what stops FastHTML prepending the
# CDN copies. See src/open_apps/frontend.py for why that matters.
hdrs = local_hdrs() + hdrs
app = FastHTML(hdrs=hdrs, *args, default_hdrs=False, **kwargs)

@app.get("/{fname:path}.{ext:static}")
def static(fname: str, ext: str):
Expand Down
3 changes: 2 additions & 1 deletion src/open_apps/apps/todo_app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import json
from typing import List
from src.open_apps.apps.start_page.helper import create_logo_header
from src.open_apps.frontend import local_hdrs


@dataclass
Expand All @@ -18,7 +19,7 @@ class Todo:
done: bool


app, rt = fast_app()
app, rt = fast_app(default_hdrs=False, hdrs=local_hdrs())
logo_title_container = None
styles = Style("")

Expand Down
69 changes: 69 additions & 0 deletions src/open_apps/frontend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""
Copyright (c) Meta Platforms, Inc. and affiliates.
All rights reserved.
This source code is licensed under the license found in the
LICENSE file in the root directory of this source tree.

Locally-served frontend headers.

``fast_app()`` and ``FastHTML()`` default to loading htmx, Pico and three
helper scripts from ``cdn.jsdelivr.net``. That is fine on a laptop and useless
on an eval node with no outbound network, where the assets never arrive and
nothing says so:

* without htmx, every ``hx-*`` attribute in every app is inert — a checkbox
still toggles visually, because that is the browser's own behaviour, but no
request is sent and no server state changes, so the task scores zero;
* without Pico, every page renders unstyled, which for a screenshot-scored
agent changes the observation itself.

Every app therefore constructs its FastHTML instance with
``default_hdrs=False`` and takes its headers from here instead. The files live
in ``apps/assets/vendor/`` and are served by the static route in
``apps/start_page/helper.py``.

Usage::

from open_apps.frontend import local_hdrs

app, rt = fast_app(default_hdrs=False, hdrs=local_hdrs())

``default_hdrs=False`` is not optional. Omit it and FastHTML prepends the CDN
tags anyway, the page works on a laptop, and the regression only shows up as a
run of zero-reward episodes on the cluster.
"""
from __future__ import annotations

from fasthtml.common import Link, Script

# Pinned by filename. FastHTML's default pulled `@picocss/pico@latest`, which
# made the styling of an eval run depend on the day it ran.
HTMX_FILENAME = "htmx-2.0.4.min.js"
PICO_FILENAME = "pico-2.1.1.min.css"

VENDOR_URL = "/assets/vendor"

HTMX_URL = f"{VENDOR_URL}/{HTMX_FILENAME}"
PICO_URL = f"{VENDOR_URL}/{PICO_FILENAME}"


def local_hdrs(pico: bool = True, htmx: bool = True) -> list:
"""Return the header elements FastHTML would otherwise load from a CDN.

Args:
pico: include the Pico baseline stylesheet. Pass ``False`` for an app
that brings its own full stylesheet and only needs the behaviour.
htmx: include htmx. Effectively always wanted — every app in this repo
drives its state changes through ``hx-*`` attributes — but kept
explicit so a static page can opt out.

Returns a fresh list each call: FastHTML mutates the ``hdrs`` list it is
given, so a shared module-level constant would accumulate one app's headers
onto the next.
"""
hdrs = []
if pico:
hdrs.append(Link(rel="stylesheet", href=PICO_URL))
if htmx:
hdrs.append(Script(src=HTMX_URL))
return hdrs
Loading
Loading