Skip to content

Opinionated Stack

This file is the source of truth for the libraries Angee binds and what each one owns. The stack is opinionated so product and addon work starts from settled choices instead of re-litigating infrastructure. If a concern is listed here, use the library's native shape and keep Angee as thin glue.

Dependency changes must update this file in the same change.

How The Stack Is Locked

  • docs/stack.md owns concern boundaries: which library owns which job, and what thin glue Angee adds.
  • pyproject.toml owns Python package metadata, workspace membership, tool configuration, and declared Python dependencies. uv.lock pins the resolved Python graph. Use uv add / uv lock; do not use pip install by hand.
  • package.json owns JavaScript package scripts and declared dependencies. pnpm-workspace.yaml owns workspace membership. pnpm-lock.yaml pins the resolved JavaScript graph. Use pnpm add / pnpm install; do not use npm or yarn.
  • A dependency change is complete only when the concern row here and the relevant manifest or lockfile agree.

Backend

PickOwnsAngee adds
Python >= 3.14Runtime and typingProject conventions
Django 6.0+ORM, migrations, admin, auth contract, app registryAbstract bases and build-time composition into runtime apps
strawberry-djangoGraphQL types, resolvers, dataloaders, schema printingMerge addon schema parts into named schemas, crud/changes shortcuts, emit SDL, serve per name
django-choices-fieldEnum-backed model fieldsStateField semantic wrapper
strawberry-django-aggregatesAggregation and group-by resolversAddon-level AggregateBuilder wiring (per addon, e.g. notes)
strawberry-django-hasuraExpose Django models in the Hasura GraphQL dialect (_bool_exp/_aggregate/x_by_pk/_set), plus computed (non-model) sources via a run_query RowSourceComposes it as the model emitter (hasura_model_resource) and the pydantic computed-source emitter (hasura_pydantic_resource)
pydanticTyped model validation/parsingRow-shape SSOT for computed (non-model) Hasura resources — the node + filter scalars derive from the pydantic model (hasura_pydantic_resource)
channels + uvicornASGI/WebSocket transport and servingGraphQL subscription mounting; uvicorn serves the composed ASGI app and sends the lifespan that enters the MCP mount's http_app lifespan (angee.asgi)
django-zed-rebacREBAC engine, actor scoping, relationship storage, local and SpiceDB-compatible backendsPer-addon schema merge, reserved roles, actor resolver
django-sqidsOpaque external IDsSqidMixin, SqidField (NULL-safe decode on joins), GraphQL boundary scalar
django-simple-historyShadow history tables and revertHistoryMixin marker
django-reversionVersioned field snapshots and revertRevisionMixin convenience API, composer-emitted model registration
cryptographyEncryption primitivesEncryptedField (Fernet at rest, secret-by-type)
django-import-export + tablibResource import/export resources, tabular formats, row cleaning, and row resultsTiered manifests, xref ledger, and frozen-tier policy
pyyamlYAML parsing substrateResource loader reads .yaml/.yml resource files; django-yamlconf consumes project settings YAML
ruamel.yamlComment/format-preserving round-trip YAML editingThe AddonInstaller's settings.yaml INSTALLED_APPS install/uninstall edit — the one writer that must preserve operator comments and layout (pyyaml round-trips lose them); not used at boot
django-yamlconfDjango settings YAML overlaysangee.compose.settings loads settings.yaml beside manage.py; Composer applies addon autoconfig.py fragments
django-environTyped boot environment access and URL parsersangee.compose.settings reads Angee bootstrap env vars
pyjwt[crypto]JWT/JOSE signature + claims verification and JWKS fetchOIDC id_token verification (OAuthClientOidcProtocol.verify_id_token); kept because authlib.jose is deprecated. The OAuth2 token exchange itself is owned by authlib
authlibOAuth2/OIDC client protocol — authorization-code + refresh-token requests, client authentication, PKCE (RFC 7636), and token revocation (RFC 7009)Thin per-OAuthClient OAuth2Client adapter behind the stable OAuthClientProtocol seam, plus the non-standard JSON-token-body shim; id_token verification stays on pyjwt
httpxHTTP client/transport for all integrate outbound callsintegrate.http.PinnedTransport — an SSRF-pinned httpx transport that resolves once and dials a validated IP (judgement owned by integrate.net.is_unsafe_address) with system-store TLS. Composed by HttpClient (the integration backends) and by the OAuth client (handed to authlib's OAuth2Client); the honest Angee-Integrate/1.0 UA and an injected transport test seam ride on it
httpcorehttpx's low-level connection pool + network backendintegrate.http._PinnedBackend subclasses httpcore.SyncBackend and overrides connect_tcp to dial the validated IP; PinnedTransport swaps it into the pool's _network_backend. Named and bounded directly because the SSRF pin owns httpcore's SyncBackend/ConnectionPool API, not just via httpx
mcp (jlowin FastMCP v2)MCP server — tool registration, JSON-RPC, StreamableHTTP ASGI app, bearer auth (TokenVerifier), per-call middlewareMounts one StreamableHTTP app at /mcp via the asgi.py http_mounts seam (its http_app lifespan entered by angee.asgi via router.lifespan_context), authenticates the bearer to a REBAC actor with a fastmcp.server.auth.TokenVerifier and brackets each tool call in that actor; addon tools — incl. GraphQLTool operations executed under the actor (angee.mcp.graphql) — run scoped, and rebac authorizes
anthropicAnthropic Claude API SDK — Messages API client, model catalogue, retries, typed SDK modelsagents_integrate_anthropic maps Angee inference providers/models to the SDK and contributes the backend into ANGEE_INFERENCE_BACKEND_CLASSES
openaiOpenAI Python SDK — Chat Completions client, model catalogue, retries, typed SDK modelsagents_integrate_openai maps Angee inference providers/models to the SDK and contributes the backend into ANGEE_INFERENCE_BACKEND_CLASSES
python-magicMIME detection from file bytesStorage finalize detection (requires the system libmagic)
vobjectvCard/iCalendar parse + serialiseparties_integrate_carddav parses CardDAV vCards into parties/handles/addresses (and serialises for round-trip)
markdown-it-pyCommonMark tokenizer with source line spans (block token .map)knowledge slices doc sections by heading without re-rendering — the MarkdownPage structure methods (parse_outline/outline, section_range, spliced_section, spliced_unique) shared by the outline read field and the section-anchored patch write
uvPython dependency resolution and workspacesWorkspace layout

Frontend

PickOwnsAngee adds
React 19View libraryComponent conventions
TypeScript >= 6Language and type systemBranded boundary types
@refinedev/coreResource registry, standard data hooks, react-query cache/invalidation, auth/i18n/live provider contractsAngee projects emitted angee.resources metadata to refine resources and mounts one composed <Refine> root with named providers and the TanStack Router binding
@refinedev/hasura + graphql-request 5 + graphql 15Hasura GraphQL data provider (_bool_exp, order_by, _aggregate, _by_pk, _set) and authored meta.gqlQuery / meta.gqlMutation executionAngee pins idType: "String" and namingConvention: "hasura-default", uses refine-compatible GraphQL document ASTs, and applies session/CSRF or service auth at the transport boundary
graphql-ws 5GraphQL WebSocket lifecycle for the Hasura live provider and daemon-owned operator transportEndpoint derivation, connection params, retry policy, and the operator daemon subscription + raw log socket transport — request/response now rides a Refine operator data provider, leaving only the intrinsically streaming surfaces on this ws transport
GraphQL Code Generator (client-preset) + @graphql-typed-document-node/coreGenerated TypeScript schema and operation types from emitted Django SDL and daemon-owned SDL, as TypedDocumentNode documents@angee/app owns the one angee-web-codegen CLI: it reads runtime/web/manifest.json, generates each Django schema from runtime/schemas/<schema>.graphql (routing documents by filename: documents.ts/documents.console.ts → console, documents.public.ts → public), derives authored action/aggregate/group/delete-preview/revision documents, and emits the composed runtime/web/app.ts. The operator daemon joins the same pass as an external [web].codegen manifest entry — its committed SDL read straight from the operator package, scanning only documents.daemon.ts, with a bare typescript types module the console re-exports; authored operations carry no hand-written result/variables types
TanStack RouterType-safe routing and search paramsdefineAddon to createApp route composition and flat URL search codec
@refinedev/react-hook-form + react-hook-form + @hookform/resolvers + zodForm state, submit lifecycle, and validation bindingFormView keeps Angee's declarative rendered DSL while delegating state/validation to refine/react-hook-form
@refinedev/react-table + TanStack TableServer-backed table state, sort/filter/pagination bridge, columns, grouping, selectionListView and BoardView keep Angee's rendered controls and domain view modes while delegating standard table/data mechanics
TanStack VirtualRow and column virtualizationLong-list wiring
nuqsType-safe URL query stateRemaining chrome query state such as top-menu tabs
i18nextRuntime i18nPer-addon namespace convention
date-fnsDate and relative-time formattingDate and timestamp widgets
use-debounceDebounced React values and callbacksSearch and filter inputs
Tailwind 4Token styling engineSemantic token set
tailwind-mergeSafe class mergingcn() helper
lucide-reactIconsName-referenced icon registry
ViteBundling, dev server, HMRProject integration
@agentclientprotocol/sdkACP client — agent JSON-RPC session, prompt/cancel, session-update stream (the agent image runs @agentclientprotocol/claude-agent-acp; both replace the deprecated @zed-industries/* names)WebSocket ndjson transport to a routed agent + assistant-ui runtime bridge
@assistant-ui/reactChat thread UI — message store, composer, tool-call renderingACP-streaming runtime adapter and styled thread surface
streamdownStreamed-markdown render for assistant chunksAssistant message body in the agent chat
react-pdf (+ pdfjs-dist)Inline PDF rendering (pdf.js)storage file previewer
@vidstack/reactInline video/audio playerstorage file previewer
heic-toClient-side HEIC/HEIF decode to a displayable image (current libheif-wasm)storage HEIC previewer
pnpmJavaScript dependency resolution and workspacesWorkspace layout
Node >= 22.13JavaScript build runtimeProject runtime

Chat-UI library choice: @assistant-ui/react owns the chat-UX surface (composed over ACP); CopilotKit and @headlessui/react were evaluated and rejected, and TanStack AI is a watch item.

Hasura Dialect Rule

strawberry-django-hasura, the operator daemon SDL, @refinedev/hasura, and Angee's refine/data glue share one Hasura-default wire contract. Because the daemon already speaks this contract, the operator web package consumes it as a Refine operator data provider (bearer-authed createAngeeHasuraDataProvider) for request/response, the same shape as the console/public providers; only its live subscriptions and the raw log socket stay on the daemon ws transport. Grouped resources must keep the DDN/NDC-preview shape: <resource>_groups(group_by, where, having, order_by, limit, offset): [<resource>_group!]!, with each group returning a typed key: <Model>GroupKey! and the free aggregate: <Model>Aggregate!. The stock <resource>_aggregate root remains the unmodified refine/Hasura aggregate surface; grouped roots are authored operations owned by the dialect adapters.

Future grouped features such as bucket ordering, bucket predicates, additional date extraction, or JSON drill-down operators must be added at the dialect owners together: the Django adapter, operator SDL, emitted resource metadata, and the refine authored-operation helpers. Do not add frontend-only group semantics or local provider dialects.

Rendered Binding

Angee's frontend is Refine-native: the app composes one <Refine> root, resource metadata projects into refine resources, and the rendered binding owns only domain presentation over refine state. The active frontend owners are @angee/app, @angee/refine, @angee/resources, and @angee/ui.

PickOwnsAngee adds
@base-ui/reactHeadless primitives: dialog, popover, menu, tabs, tooltip, field, toolbar, scroll area, and related UIStyled binding and composition rules; controlled open/onOpenChange owns popover/dialog transition timing
@floating-ui/react-domFloating-element positioning and virtual anchorsPopover and menu anchoring
@angee/logo-reactAngee brand logo and cube marksBrand lockup in the public layout
react-markdown + remark-gfmMarkdown rendering (GitHub-flavored)Markdown widget preview
tailwind-variantsVariant recipes with slotsComponent recipes
tw-animate-cssTailwind 4 animation utilitiesMotion tokens
cmdkCommand menuSpotlight command surface
react-day-pickerCalendarDate widgets
react-resizable-panelsSplit panesLayout and inspector panes
CodeMirror 6 (+ @codemirror/lang-json)Text / Markdown / JSON editorMarkdown and JSON widget editors (shared useCodeMirrorEditor)
@xyflow/reactnode/edge graph canvas@angee/base GraphView canvas
@dagrejs/dagredirected-graph layout@angee/base GraphView node placement
@dnd-kitDrag and dropBoard and rail interactions
Native browser drag/dropFile drag enter/leave/drop events and DataTransfer.files@angee/base upload drop target primitive

Tooling

PickOwnsAngee adds
Cobraangee CLI implementationDev supervisor, init, workspaces, templates
hatchlingPython wheel buildPackage metadata conventions
ruffPython lint and formatRepo checks
mypyPython type checkingStrict backend checks
pytest + pytest-djangoBackend testsSynthetic project and integration fixtures
FakerTest and seed data generationBulk lorem fixtures (e.g. seed_lorem_notes)
VitestTypeScript and React testsFrontend unit checks
happy-domDOM environment for VitestPer-file env opt-in for hook and component tests
@testing-library/reactReact component and hook test renderingProvider-wrapped render and hook harnesses
PlaywrightBrowser tests@angee/e2e harness: workspace-isolated runner, role storageState login, GraphQL api fixture, Page Object base (docs/testing/e2e.md)
@playwright/mcpInteractive browser-driving for host coding agentsRepo-root .mcp.json server (npx-run, pinned), bound to the base stack's chrome-profile (.angee/data/chrome); the agent navigates to the stack's ANGEE_UI_PORT (:5173). Distinct from @angee/e2e (the deterministic test runner) and agents.MCPServer (the MCP config rendered for operator-provisioned product agents)
StorybookComponent workshop@angee/ui and addon previews
GitHub ActionsCIBuild, lint, type, test gates
CopierProject and addon templatesAngee templates

Proposed, Not Locked

PickRole
Yjs + HocuspocusCollaborative editing
Celery + django-celery-beatQueues and schedules
pgvector / sqlite-vec / python-igraph / lightrag-hkuVector search and graph RAG
django-ninjaTyped REST sidecars (callbacks, webhooks, health) — over the locked pydantic
boto3S3-compatible storage backend (S3 / R2 / MinIO presigned IO)
@xyflow/reactGraph and canvas (node/edge) views
react-json-view-lite + ansi-to-reactJSON widget read tree, debug/log JSON + ANSI panels
simple-icons + @lobehub/iconsBrand and vendor SVG icon registry

Change Policy

  • Add a dependency only with an owner row here.
  • Remove a dependency by deleting its row.
  • Swap a dependency by updating the row and explaining why in the change.
  • Move proposed picks into a locked section before shipping code that depends on them.

Released under the AGPL-3.0 License.