Skip to content

Dev Server Hot Reloading - #76

Closed
aaronsmulktis wants to merge 4 commits into
facebookresearch:mainfrom
aaronsmulktis:aaronsmulktis/dev-server
Closed

Dev Server Hot Reloading#76
aaronsmulktis wants to merge 4 commits into
facebookresearch:mainfrom
aaronsmulktis:aaronsmulktis/dev-server

Conversation

@aaronsmulktis

Copy link
Copy Markdown
Contributor

No description provided.

aaronsmulktis and others added 4 commits August 5, 2026 11:42
- theme design vars
- set default layout
- set default theme
- use design vars in todo app
- update README
- add theme & layout to appserver
- retain appearance for legacy usage
- add theme & layout to registry and session
- 44 tests passing
./scripts/dev.sh -- save anything under src/ or config/ and the server
restarts and the browser refreshes itself.

serve() already defaults to reload=True and launcher.launch_apps() turns it
off, so the obvious fix is to flip it back. That gives a server that reloads
into a broken state, for two reasons, both now recorded in dev.py:

1. The app is configured imperatively after import. launch.py exposes a bare
   app; routes and config are attached later by
   initialize_routes_and_configure_task() inside launcher.launch(). The
   reloader restarts the worker and re-imports from the import string, running
   none of that -- a freshly imported app has 7 routes and no .config, so every
   app 404s and anything touching config raises. dev.py does the configuration
   at module level instead, so re-importing rebuilds a wired app.

2. Re-seeding over existing rows fails. set_environment() inserts without
   clearing, so a second run hits unique-constraint violations and the code
   editor bails out instead of re-seeding. The reloader restarts against the
   same directory, so every reload after the first would hit both. dev.py wipes
   its state directory first, which also means what you are looking at matches
   the seed state an eval episode gets.

The state directory lives in the system temp dir, deliberately outside the
repo. The code editor seeds .py files on startup, and with the directory at
./.dev that produced an infinite loop -- seed, watcher fires, restart, seed.
Worth knowing: that happened even though uvicorn reported watching only src/
and config/, so confining it by --reload-dir is not something to rely on.

Browser refresh reuses FastHTML's two live-reload halves -- the /live-reload
websocket and the client snippet -- attached to the existing app rather than
swapping it for FastHTMLWithLiveReload. Subclassing only in development would
mean the thing being tested is not the thing an eval runs.

No default Hydra overrides, so this boots whatever the current branch's default
config is rather than depending on config groups that exist only on some
branches. Pass OPENAPPS_DEV_OVERRIDES to pick a layout or theme.

Verified end to end on main: server boots, / and /todo return 200, live-reload
script is injected, no reload loop. On the desktop branch: edit a file, one
reload fires, the change appears in served output, /todo re-seeds cleanly with
zero constraint errors, and a syntax error mid-edit fails loudly and recovers
on the next save.
@aaronsmulktis
aaronsmulktis requested a lite review from Copilot August 25, 2026 17:43
@aaronsmulktis aaronsmulktis self-assigned this Aug 25, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Aug 25, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a dedicated hot-reloading development server entrypoint (Uvicorn reload + browser live-reload) and introduces a shared “design tokens” theming system via Hydra-configured YAML theme files, plus initial layout config group files for the todo app.

Changes:

  • Add dev.py + scripts/dev.sh to run a reloadable dev server that rebuilds app config/routes on import and injects FastHTML live-reload.
  • Introduce src/open_apps/theme.py and multiple config/apps/theme/*.yaml files to define shared CSS custom-property tokens.
  • Add config/apps/todo/layout/* configs to begin separating todo “layout” from “appearance”.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
dev.py Hot-reloading dev server module that composes Hydra config, wipes dev state dir, wires routes, and installs live-reload.
scripts/dev.sh Convenience script to run uvicorn dev:app with reload watching src/ and config/.
src/open_apps/theme.py Theme loading/resolution + CSS token rendering helper module.
config/apps/todo/layout/default.yaml Adds a “default” todo layout config group.
config/apps/todo/layout/kanban_board.yaml Adds a “kanban_board” todo layout config group.
config/apps/theme/default.yaml Default shared token theme.
config/apps/theme/dark.yaml Dark theme token set.
config/apps/theme/mono.yaml Mono theme token set.
config/apps/theme/material.yaml Material-inspired theme token set.
config/apps/theme/bootstrap.yaml Bootstrap-inspired theme token set.
config/apps/theme/solarized.yaml Solarized theme token set.
config/apps/theme/challenging_font.yaml Challenging-font theme token set.
Suppressed comments (2)

dev.py:55

  • The reloader may import this module multiple times in the same interpreter (you even mention this later), but hydra.initialize(...) will raise if Hydra is already initialized. Importing GlobalHydra (and using initialize_config_dir below) lets _build() safely clear Hydra state before recomposing config.
from hydra import compose, initialize

dev.py:100

  • initialize(..., config_path="config") depends on the current working directory and will also fail if Hydra is already initialized. Clearing GlobalHydra and composing via initialize_config_dir with an absolute config path makes reloads and python dev.py (from any cwd) more reliable.
    with initialize(version_base=None, config_path="config"):
        config = compose(
            config_name="config",
            overrides=[f"logs_dir={DEV_DIR}", "use_wandb=False", *overrides],
        )

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread dev.py
Comment thread dev.py
Comment on lines +82 to +84
#: Pass a space-separated list of Hydra overrides to pick something else:
#: OPENAPPS_DEV_OVERRIDES="apps/theme=dark apps/todo/layout=kanban_board"
DEFAULT_OVERRIDES = ""
Comment thread scripts/dev.sh
Comment on lines +2 to +3
# Structure only -- colors/fonts come from the shared theme. Select with
# `apps/todo/layout=kanban_board`.
Comment thread src/open_apps/theme.py
Comment on lines +61 to +75
def load_theme(name: str) -> dict:
"""Load a theme's tokens from ``config/apps/theme/<name>.yaml``.

Returns a dict with at least ``name``, ``tokens`` and ``import_url``.
Falls back to the default theme when ``name`` is unknown so a bad
override degrades gracefully instead of raising.
"""
path = _THEME_DIR / f"{name}.yaml"
if not path.exists():
path = _THEME_DIR / f"{_DEFAULT_THEME}.yaml"
data = yaml.safe_load(path.read_text()) or {}
data.setdefault("name", name)
data.setdefault("tokens", {})
data.setdefault("import_url", "")
return data
Comment thread src/open_apps/theme.py
Comment on lines +15 to +20
Selection is done with Hydra overrides:

* ``apps/theme=solarized`` -> global default for every app
* ``apps.todo.theme=solarized`` -> override a single app (falls back to
the global theme when the app's ``theme`` field is null/unset)

@marksibrahim marksibrahim left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wonderful to have this, thank you! Would you mind adding a small note about how to launch with hot reload in the README or docs?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants