Bundle Website¶
This package contains the FastAPI website for The Bundle.
High-level architecture¶
App entrypoint:
src/bundle/website/__init__.pyCore app factory and policies:
src/bundle/website/core/Reusable page mounting primitives:
src/bundle/website/core/pages.pyReusable component import surface:
src/bundle/website/core/components.pyTheBundle page registry:
src/bundle/website/sites/thebundle/pages/__init__.pyShared page/template helpers:
src/bundle/website/core/templating.pyShared layout + global theme:
src/bundle/website/templates/base.html,src/bundle/website/static/theme.cssBuiltin component implementations:
src/bundle/website/builtin/component/Frontend workspace (npm + tsconfig):
src/bundle/website/sites/thebundle/
The app mounts:
/static->src/bundle/website/static/components-static->src/bundle/website/builtin/component(served throughComponentStaticFilessuffix allowlist)
Install and run¶
Install website extras:
pip install -e ".[website]"Start server:
bundle website site start bundleOpen:
http://127.0.0.1:8000/
Frontend build commands¶
Install frontend tooling/deps:
bundle website installBuild frontend assets:
bundle website site build bundleType-check website TS only:
cd src/bundle/website/sites/thebundle && npm run check:website-ts
Pages¶
Pages are registered per site using PageDefinition.
Default site registry:
src/bundle/website/sites/thebundle/pages/__init__.py
Each page module typically defines:
routerTEMPLATE_PATHSTATIC_PATHone or more route handlers returning
TemplateResponse
Reusable mount functions live in src/bundle/website/core/pages.py.
initialize_pages(app, pages) mounts every page router and page static folder and publishes nav data on app.state.
Component system¶
Components are page-scoped and explicit:
Import component APIs from
bundle.website.core.components(orfrom bundle.website.core import components).Create component instances in the page module.
Attach websocket/API routes with
components.attach_routes(router, *COMPONENTS).Pass render/assets context with
components.context(*COMPONENTS).Render in template via
templates/components/macros.html.
The macros provide:
styles(component_assets)-> emits component CSS linksscripts(component_assets)-> emits component JS linksrender(components)-> includes each component template
How to create a new component¶
This is the current recommended flow.
1. Create component folder¶
Use one folder per component:
src/bundle/website/builtin/component/<domain>/<name>/
component.py
template.html
component.css (optional)
component.ts (optional)
component.js (built)
assets/ (optional)
2. Choose a base class¶
Websocket component: inherit
WebSocketBaseComponentfile:
src/bundle/website/builtin/component/websocket/base/component.py
Graphics component: inherit
GraphicBaseComponent/ typed 2D/3D variantsfile:
src/bundle/website/builtin/component/graphic/base/component.py
The base classes auto-hydrate:
templatefrom localtemplate.htmlassetsfrom local component root (component.css,component.js,component.mjs)
3. Implement component.py¶
Minimal websocket example:
from ..base import WebSocketBaseComponent, WebSocketComponentParams
class WebSocketExampleComponent(WebSocketBaseComponent):
component_file: str = __file__
slug: str = "ws-example"
name: str = "WebSocket Example"
description: str = "Example websocket component."
params: WebSocketComponentParams = WebSocketComponentParams(endpoint="/ws/example")
Override handle_websocket(self, websocket) only when you need custom runtime behavior.
4. Build template.html¶
Use component.slug/component.params.endpoint pattern and stable data-* selectors for JS hooks.
Websocket UI should use shared panel structure classes:
root:
ws-panel <component-class>blocks:
ws-panel__header,ws-panel__badges,ws-panel__viewport,ws-panel__controls, etc.
5. Add frontend assets¶
Put CSS/JS at component root (component.css, component.ts -> component.js).
For websocket components:
shared base stylesheet is loaded automatically via
WebSocketBaseComponent.shared_assetscurrent shared stylesheet:
websocket/base/component.csslocal component CSS should mostly set variables and minimal overrides
6. Attach component to a page¶
In page module (for example sites/thebundle/pages/playground/page.py):
from bundle.website.core import components
COMPONENTS = (
components.WebSocketECCComponent(),
)
components.attach_routes(router, *COMPONENTS)
In the page handler:
context = base_context(request, components.context(*COMPONENTS))
return templates.TemplateResponse(request, "playground.html", context)
In the page template:
{% import "components/macros.html" as component_macros with context %}
{% block styles %}
{{ component_macros.styles(component_assets) }}
{% endblock %}
{% block content %}
{{ component_macros.render(components) }}
{% endblock %}
{% block scripts %}
{{ component_macros.scripts(component_assets) }}
{% endblock %}
Websocket internals¶
src/bundle/website/builtin/component/websocket/base provides:
route/runtime helpers:
create_router,run_websocket,every,drain_text,receive_json,keepalive_looptyped message models:
KeepAliveMessage,AckMessage,ErrorMessagemessage dispatch helper:
MessageRouter
Use these blocks instead of custom ad-hoc websocket loops when possible.
Security and static serving notes¶
Component static mount only serves allowed static asset suffixes (
.css,.js,.mjs,.map, fonts/images, etc.).Python source files under
builtin/component/are not exposed by/components-static.
Excalidraw vendor workflow¶
Vendor source:
src/bundle/website/vendor/excalidrawServed build:
src/bundle/website/sites/thebundle/pages/excalidraw/static/excalidraw-web
Typical update flow:
git submodule update --init --recursivecheckout desired vendor ref
build vendor app
copy built assets into
sites/thebundle/pages/excalidraw/static/excalidraw-web