Dev Server Hot Reloading - #76
Closed
aaronsmulktis wants to merge 4 commits into
Closed
Conversation
- 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
[Improvement] Todo layout
./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.
Contributor
There was a problem hiding this comment.
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.shto run a reloadable dev server that rebuilds app config/routes on import and injects FastHTML live-reload. - Introduce
src/open_apps/theme.pyand multipleconfig/apps/theme/*.yamlfiles 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. ImportingGlobalHydra(and usinginitialize_config_dirbelow) 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. ClearingGlobalHydraand composing viainitialize_config_dirwith an absolute config path makes reloads andpython 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 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 on lines
+2
to
+3
| # Structure only -- colors/fonts come from the shared theme. Select with | ||
| # `apps/todo/layout=kanban_board`. |
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 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
approved these changes
Sep 1, 2026
marksibrahim
left a comment
Contributor
There was a problem hiding this comment.
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?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.