Skip to content

tuiti - #484

Open
svobodavid-svg wants to merge 145 commits into
anthropics:mainfrom
svobodavid-svg:main
Open

tuiti#484
svobodavid-svg wants to merge 145 commits into
anthropics:mainfrom
svobodavid-svg:main

Conversation

@svobodavid-svg

Copy link
Copy Markdown

Description

Quickstart

  • Computer Use Demo
  • Customer Support Agent
  • Financial Data Analyst
  • N/A

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Code refactoring
  • Other (please describe):

Testing

  • Added/updated unit tests
  • Tested manually
  • Verified in development environment

Screenshots

Additional Notes

claude and others added 30 commits June 23, 2026 22:25
…config

- providers/: AbstractLLMProvider protocol + Claude and Gemini implementations
- core/router.py: LLMRouter with static/failover/round_robin strategies
- agents/: AgentRole, AgentOutput, SingularitySwarm with multi-LLM routing
- config/settings.py: dual-API-key config (Anthropic + Gemini)
- memory/embeddings.py: offline HashEmbeddingFunction (128-dim, no ONNX)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
Full implementation of the Singularity external extension built on Omega:

- core/telemetry.py: structlog + Prometheus metrics (graceful no-op fallback)
- core/limiter.py: ProviderRateLimiter token bucket per provider (aiolimiter)
- core/router.py: 6 routing strategies (static/failover/round_robin/
  cost_optimized/latency_optimized/quality_first) + self-healing cooldown
- providers/: cost/latency/quality metadata + health/cooldown self-healing
- agents/swarm.py: rate-limited invocation, failover, latency telemetry
- core/graph.py: SingularityCore LangGraph loop + provider_log tracking
- api/main.py: lifespan handler + /providers, /router/strategy, /metrics,
  /tasks/{id}/providers, force_provider
- memory/evals/rag: ported from Omega with all 8 critical fixes preserved
- tests/: 37 offline tests (unit/chaos/perf) all green, plus integration
- pyproject.toml, .env.example, README.md, scripts/run_tests.sh

All 8 Omega critical fixes preserved. 37/37 offline tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
- core/session_store.py: thread-safe in-memory session store s konverzační
  historií a kumulativními náklady (estimate_cost per provider)
- core/graph.py: přidán run_stream() AsyncGenerator — node-level progress
  events přes graph.astream(); zachováno zpětně kompatibilní run()
- api/main.py: POST /task/stream (SSE), GET /sessions/{uid},
  GET /sessions; WebSocket přepnut na run_stream() → klient vidí
  progress po každém uzlu (plan/execute/critique/synthesize/reflect)
- memory/embeddings.py: opravena kompatibilita s ChromaDB >=0.6
  (HashEmbeddingFunction nyní implementuje name() a is_legacy())
- tests: 11 nových unit testů (session_store × 7, streaming × 4)
  — 48/48 offline testů zelených
… dashboard

- core/health_monitor.py: background asyncio task, periodický health check
  všech providerů (default 30s); při obnově po cooldownu volá record_success()
- api/dashboard.py: self-contained admin dashboard HTML (vanilla JS, žádné CDN)
  — live provider status tabulka, session přehled s náklady, Prometheus raw,
  runtime změna strategie; auto-refresh každých 5s
- core/graph.py: SingularityState rozšířen o session_context; _plan_node
  injektuje posledních 3 turny konverzace jako přidaný kontext plánovači
- api/main.py: _build_session_context(), HealthMonitor spuštěn v lifespan,
  GET /health/providers (okamžitý check), GET /dashboard
- tests: 10 nových unit testů (health_monitor × 5, multiturn × 5)
  — 58/58 offline testů zelených
…on export

- core/task_queue.py: in-memory async fronta (QUEUED→RUNNING→COMPLETED/FAILED),
  jeden worker, start/stop v lifespan; v produkci nahradit Celery/arq
- api/main.py: POST /task/async (okamžitá odpověď s task_id),
  GET /task/{id}/status, GET /task/{id}/result,
  POST /task/compare (paralelní Claude vs Gemini přes asyncio.gather),
  GET /sessions/{uid}/export (JSON download s eval_scores)
- core/session_store.py: ConversationTurn rozšířen o eval_scores: dict
  (zpětně kompatibilní, default={}); to_dict() zahrnuje eval_scores
- tests: 10 nových unit testů (task_queue × 5, compare × 5)
  — 68/68 offline testů zelených
…llbacks

- Add BudgetManager with thread-safe per-user USD cost limits; blocks task
  submission when limit would be exceeded (HTTP 402)
- Add callback_url to TaskRequest and QueuedTask; _fire_webhook() posts result
  via httpx.AsyncClient after task completes or fails (graceful failure)
- Add POST /task/batch endpoint (max 10 tasks, budget-checked per task)
- Add GET /queue/status endpoint returning current queue depth
- Add POST/GET/DELETE /budget/{uid} endpoints for runtime limit management
- Wire budget_manager into POST /task/async enforcement
- Add 5 unit tests for BudgetManager and 3 unit tests for webhook callbacks
  (70 unit tests total, all offline)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
…ong-poll wait

- TaskPriority enum (CRITICAL > HIGH > NORMAL > LOW); upgrade TaskQueue to
  asyncio.PriorityQueue; priority field on QueuedTask + TaskRequest
- Event-based wait() in TaskQueue — GET /task/{id}/wait long-polls without
  busy-polling, clamped 1–300 s, returns 408 on timeout
- UserRateLimiter: sliding-window (60 s) RPM counter per user_id; enforced in
  POST /task/async (HTTP 429); POST/GET/DELETE /rate-limits/{uid} endpoints
- Fix HashEmbeddingFunction: name()/supported_spaces()/get_config() as
  @classmethod to satisfy ChromaDB >=0.6 call convention; 0 warnings now
- 10 new unit tests (5 priority queue + 5 user limiter) → 89 total

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
- RetryPolicy: immutable config (max_attempts, backoff_base, max_backoff,
  jitter); delay_for_attempt() computes capped exponential + jitter delay
- TaskQueue: failed tasks with remaining attempts sleep+requeue with backoff;
  after exhaustion → DLQ dict; TaskStatus.RETRYING + TaskStatus.DLQ added
- retry_from_dlq(): reset attempt counter, re-enqueue from DLQ
- AuditLog: thread-safe ring buffer (deque maxlen=1000) recording
  task_submitted/completed/failed/retried/dlq and budget/rate-limit events
- New API endpoints: GET /audit-log (filterable), GET /dead-letter-queue,
  POST /dead-letter-queue/{id}/retry; max_retries field on TaskRequest
- audit_log wired into lifespan and task_queue.start()
- 10 new unit tests (5 retry + 5 audit log) → 99 total, all offline

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
data/ directories generated by ChromaDB during test runs were showing
up as untracked. Covered by the per-project .gitignores inside each
quickstart, but the root had none — adding one to prevent re-occurrence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
- api/auth.py: FastAPI dependency verify_api_key via X-API-Key header;
  passes through anonymously when require_api_key=False (dev mode)
- core/api_keys.py: ApiKeyManager — thread-safe in-memory store,
  prefix sk-sg-, create/revoke/validate/list/delete_user_keys
- api/main.py: POST/GET/DELETE /api-keys endpoints; Depends(verify_api_key)
  guard on /task and /task/async; lifespan wires set_manager + num_workers
- config/settings.py: require_api_key (default False) + task_workers (default 1)
- core/task_queue.py: replaced single _worker_task with _worker_tasks list;
  start() accepts num_workers, spawns N asyncio worker coroutines
- tests/unit/test_api_keys.py: 7 tests for ApiKeyManager
- tests/unit/test_multi_worker.py: 3 tests for multi-worker parallelism

All 100 unit tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>
- core/request_context.py: contextvars-based per-request state
  (request_id, user_id); set/get/clear helpers
- api/middleware.py: RequestContextMiddleware — generates/propagates
  X-Request-ID header, adds X-Response-Time, logs every request at DEBUG
- api/main.py: mount RequestContextMiddleware; add GET /health/live
  (always 200) and GET /health/ready (503 until core initialised)
- Dockerfile: multi-stage build, non-root user (uid 1001),
  HEALTHCHECK via /health/live
- docker-compose.yml: singularity service + optional prometheus profile
- prometheus.yml: scrape config for /metrics
- .dockerignore: excludes tests/, data/, __pycache__, .env
- tests/unit/test_middleware.py: 5 tests — header injection, echo,
  propagation into handler, uniqueness per request

All 105 unit tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>
- cli/main.py: Typer CLI with subcommands:
    serve, health, keys create/list/revoke, queue status, dlq list/retry
  Set SINGULARITY_URL env var or pass --url to target a running server.
  Registered as 'singularity' entry point in pyproject.toml.
- core/graceful_shutdown.py: GracefulShutdown — waits for asyncio
  PriorityQueue.join() (up to timeout_s), then cancels workers;
  registers SIGTERM/SIGINT handlers on the running event loop
- api/main.py: lifespan now uses GracefulShutdown.drain() instead of
  bare task_queue.stop(), ensuring in-flight tasks complete on shutdown
- pyproject.toml: typer>=0.12.0 dep + 'singularity' script entry point +
  'cli' added to hatch wheel packages
- tests: test_cli.py (6 tests, patches _get/_post/_delete helpers),
  test_graceful_shutdown.py (5 tests)

All 116 unit tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>
- core/log_buffer.py: LogBuffer — thread-safe deque ring buffer that
  doubles as a structlog processor; captures event_dict before renderer
- core/logging_config.py: configure_logging() — wires structlog with
  merge_contextvars (propagates X-Request-ID from Fáze 8), add_log_level,
  TimeStamper, optional LogBuffer, and JSON or ConsoleRenderer
- config/settings.py: log_format (default "console", "json" for prod)
  + log_buffer_size (default 500)
- api/main.py: log_buffer singleton; configure_logging() called first
  in lifespan; GET /logs/recent?limit=N&level=L endpoint
- tests/unit/test_logging_config.py: 6 tests (capture, maxlen, level
  filter, limit, json format, clear)

All 122 unit tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>
- core/task_events.py: new TaskEventBus (asyncio pub/sub, lazy Lock,
  per-subscriber asyncio.Queue, subscribe/unsubscribe/publish/subscriber_count)
- core/task_queue.py: integrate TaskEventBus — publish state transitions
  (RUNNING, COMPLETED, RETRYING, FAILED, DLQ) to all live subscribers
- api/main.py: GET /task/{task_id}/stream SSE endpoint — streams live
  task lifecycle events; returns cached result immediately for finished tasks,
  auto-unsubscribes on disconnect or 300 s timeout
- tests/unit/test_task_events.py: 5 offline tests for TaskEventBus

127 unit tests passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
…oints

- core/cache.py: ResponseCache — SHA-256-keyed, async-safe OrderedDict
  with LRU eviction, per-entry TTL, hit/miss/eviction stats, hit_rate
- config/settings.py: enable_cache, cache_ttl_s (300s), cache_max_size (1000)
- api/main.py: cache lookup before core.run() in POST /task; store result
  after successful LLM call; GET /cache/stats + DELETE /cache endpoints
- tests/unit/test_cache.py: 10 offline tests (TTL expiry, LRU eviction,
  invalidate, clear, deterministic keys, hit_rate)

137 unit tests passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
…aph node

- core/tracing.py: setup_tracing() (in-memory or OTLP gRPC), get_tracer(),
  get_finished_spans(limit), clear_spans(); lazy InMemorySpanExporter in dev mode
- core/graph.py: each LangGraph node (plan, execute, critique, synthesize,
  reflect) wrapped in a named OTel span with session_id / provider / risk_score
  attributes; self.tracer initialised in __init__
- config/settings.py: enable_tracing (True) and otlp_endpoint ("") settings
- api/main.py: setup_tracing() called in lifespan; GET /traces endpoint returns
  last N finished spans from in-memory exporter
- tests/unit/test_tracing.py: 8 offline tests (session-scoped provider init,
  per-test exporter clear, span attributes, duration, limit, parent/child)

145 unit tests passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
claude and others added 30 commits July 4, 2026 16:11
Golden-dataset evaluation with a pass/fail gate for CI regression
guarding. Register labelled cases, run the system-under-test with a
scoring function, and get an aggregate report plus a boolean gate
(mean score >= threshold) CI can fail on.

Core module (core/eval_harness.py):
- EvalHarness: add_case/add_cases/clear; run(predict_fn, scorer,
  threshold, pass_score) → EvalReport (total/passed/failed/mean_score/
  pass_rate/gate_passed/per-case)
- predict_fn may be sync or async; predictor exceptions become case
  failures (not crashes)
- Built-in deterministic scorers: exact_match, contains, jaccard,
  numeric_close(tolerance) — offline
- Metrics: cases, runs, gate_failures

CI gate (.github/workflows/singularity-tests.yaml):
- New workflow scoped to singularity/** — installs and runs the full
  offline unit+integration suite on PRs and pushes to main, failing
  the build on any regression (no API keys needed)

API endpoints (api/main.py +1):
- POST /evals/score — score pre-computed expected/actual pairs with a
  named scorer and return the pass/fail gate report

Tests (tests/unit/test_eval_harness.py):
- 19 offline tests, all passing
- Scorers (exact/contains/jaccard/numeric_close), case mgmt, all-pass
  gate, below-threshold gate fail, partial-credit, async predict,
  predictor-exception-as-failure, per-case pass_score, invalid
  threshold, empty harness, report shape, metrics

Total: 1413 tests passing (1394 + 19 new).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
Production Kubernetes manifests wiring the existing container (port
8001) into a scalable, self-healing deployment. The Dockerfile already
existed; this adds the orchestration layer.

deploy/k8s/:
- deployment.yaml — 2 replicas, resource requests/limits, and probes:
  * liveness  → GET /healthz      (aggregated subsystem health, Fáze
    57; 503 on required-component-down → restart)
  * readiness → GET /health/ready (gate traffic until lifespan startup
    finishes, Fáze 8)
  Sets STATE_BACKEND=redis / REDIS_URL so state is shared across
  replicas via the State Store (Fáze 62); API key from a Secret.
- service.yaml — ClusterIP :80 → named http port
- hpa.yaml — HPA CPU 70%, 2–10 replicas (note on scaling by SLO burn
  rate via Prometheus Adapter)
- README.md — build/apply steps + probe/state rationale

Tests (tests/unit/test_deploy_manifests.py):
- 13 validation tests, all passing
- Manifests present + parse; Deployment kind; container port matches
  the Dockerfile's 8001; liveness=/healthz, readiness=/health/ready;
  PROBE PATHS EXIST AS REAL APP ROUTES; resource limits; redis env;
  Service selector matches pod labels + named targetPort; HPA targets
  the Deployment with sane replica bounds

Total: 1426 tests passing (1413 + 13 new).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
In-memory dense retriever: index documents by their embedding (via the
pluggable EmbeddingProvider, Fáze 61) and retrieve top-k by cosine
similarity. The semantic counterpart to the lexical BM25 Retriever
(Fáze 37) — the two fuse via the Hybrid Reranker (Fáze 38) for hybrid
search.

Core module (core/vector_store.py):
- VectorStore(embedder): add / add_many / remove / clear / size / dim
- search(query, top_k, min_score) → cosine k-NN, score-sorted with
  stable doc_id tie-break, per-hit rank + metadata
- embeds text on ingest; shares the API's embedding provider so a real
  embedder swaps in transparently; class swappable for an ANN index
  behind the same add/search surface at scale
- Metrics: indexed, dim, searches, total_hits, avg_hits
- Thread-safe via threading.Lock

API endpoints (api/main.py +4):
- POST   /vectors/index    — bulk index (embedded on ingest)
- POST   /vectors/search   — semantic cosine k-NN
- DELETE /vectors          — clear index
- GET    /vectors/metrics  — store metrics

Tests (tests/unit/test_vector_store.py):
- 18 offline tests, all passing
- Indexing (add/many/overwrite/remove/clear/dim), search (empty/
  invalid-top_k/semantic-ranking/top_k/sequential-ranks/min_score/
  metadata), hit shape, injected embedder, metrics

Total: 1444 tests passing (1426 + 18 new).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
)

Add a typed async SDK over the Singularity HTTP API:
- sdk/client.py: SingularityClient async httpx wrapper with typed methods
  for health, NLP, embeddings/vectors, RAG (lexical), and utility endpoints.
  Accepts an injectable transport so tests drive the in-process app offline.
- sdk/__init__.py: package exports (SingularityClient, export_openapi).
- sdk/export_openapi.py: CLI to dump the OpenAPI schema for codegen.
- tests/unit/test_sdk_client.py: 13 offline tests via ASGITransport.

Completes the v2.0 improvement plan (#1 embeddings through #10 SDK).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
Serve a self-contained HTML page at GET /ui (mirrors the /dashboard pattern
in api/dashboard.py). The form posts same-origin to POST /task, so it works
live with no CORS/CSP friction once the server runs. Shows the response,
provider log, eval scores, and readable 4xx/5xx error details (invalid key,
budget, low credit). Optional X-API-Key and Base URL fields for non-default
setups.

- api/task_ui.py: get_task_ui_html()
- api/main.py: GET /ui route + endpoint doc header
- tests/unit/test_task_ui.py: 2 offline tests (route 200/html + html shape)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
TaskQueue.submit() gains optional backoff_base/max_backoff/jitter params
(None → existing RetryPolicy defaults, so production behaviour is unchanged).
Retry unit tests pass a near-zero backoff instead of really sleeping through
2s+4s exponential delays, cutting test_retry.py from ~8s to ~0.2s.

Optimization round 1 (test/CI speed). Full suite green: 1459 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
….py split PoC)

Start the maintainability refactor of the api/main.py monolith (3743 lines):
- api/state.py: shared runtime singletons (embedding_provider, vector_store),
  imported by both main and routers to avoid circular imports.
- api/routers/vectors.py: the 4 /vectors endpoints as an APIRouter, registered
  via app.include_router. Routes and behaviour identical (verified live 200s).
- api/main.py: 3743 -> 3702 lines; unused imports removed.
- test_deploy_manifests: read route .path defensively (include_router adds a
  mount entry without .path).

Optimization round 3a (maintainability). Full suite green: 1459 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
The build workflow triggered on any .github/** change, so adding the
singularity test workflow dragged PR #45 into a computer-use-demo Docker
build on 16-core amd64/arm64 runners the fork doesn't have — the jobs sat
queued forever. Narrow the trigger to computer-use-demo/** so unrelated
changes no longer kick off (and get stuck on) the image build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
Spustit (launch) the new multimedia studio with full capabilities:

- Image generation with customizable styles, emotions, and filters
- Video generation with async processing and progress tracking
- AI chat interface with character profile support
- Gallery management for all generated content
- Settings panel with API key and gems management
- Dark theme UI with Tailwind CSS and shadcn components
- Zustand store for client-side state management
- Promptchan API integration for all multimedia operations
- TypeScript support for type safety and better DX

Features:
- Multiple image styles (Cinematic, Anime, Hyperreal, etc.)
- Video aspects (Portrait, Landscape, Square)
- Quality levels (Ultra, Extreme, Max)
- Customizable emotions and filters
- Real-time video processing status
- Persistent API key storage
- Responsive design

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Continue the main.py split: move Embeddings, State, Streaming, Tenancy,
Coalescer, and Evals endpoints into api/routers/*, with their singletons
(state_store, stream_metrics, tenants, coalescer) in api/state.py. Snapshot
stays in main.py (coupled to the feature-flags singleton).

Routes and behaviour identical — all 194 OpenAPI paths preserved and every
extracted endpoint verified live (200s, incl. /state/metrics route ordering
and SSE streaming). api/main.py: 3702 -> 3479 lines.

Optimization round 3b (maintainability). Full suite green: 1459 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
Plots compass azimuths from a GPS point onto a satellite-image snapshot
(Esri World Imagery, no API key needed), for aiming a directional/sector
antenna. A best-effort obstruction-height estimate is derived from shadows
detected in the image combined with the astronomically-true sun position —
public satellite basemaps don't expose per-tile sensor viewing geometry, so
this is a documented heuristic rather than a rigorous photogrammetric
correction. Includes a .claude/skills/azimuth-satellite skill that drives
the CLI conversationally and publishes the result as an Artifact.


Claude-Session: https://claude.ai/code/session_01FcK727sEgu9Y93wNqeXisu

Co-authored-by: Claude <noreply@anthropic.com>
Testing the skill's actual publish step (Artifact tool) surfaced a real
gap: render_html() emitted a full <!doctype html><html><head><body>
document, but Artifact expects a content fragment it wraps in its own
skeleton — publishing the old output risked nested/broken markup.

Rebuilds the output as a themed instrument-panel fragment: light/dark
CSS custom properties per the three-state (system/light/dark) contract,
IBM Plex Sans/Mono via Google Fonts (the one external host Artifact's
CSP allows), and a proper readout panel (ray chips, sun reading, a
confidence-pill shadow-estimate card) in place of the old plain-text
legend lines. The SVG ray/wedge/compass/scale-bar drawing logic is
unchanged.


Claude-Session: https://claude.ai/code/session_01FcK727sEgu9Y93wNqeXisu

Co-authored-by: Claude <noreply@anthropic.com>
Continue the api/main.py split: move the Fáze 34–54 endpoint block (57 routes)
into four themed APIRouters — api/routers/{retrieval,nlp,text_ops,stats}.py —
with their 21 singletons relocated to api/state.py. Singletons keep their
underscore-prefixed names so the extracted endpoint bodies (which alias e.g.
a local 'chunker' alongside the '_chunker' singleton) stay verbatim.

Routes and behaviour identical — all 194 OpenAPI paths preserved, full suite
green (1459 passed), and every extracted endpoint verified live (incl. the
/chunk custom-parameter branch). api/main.py: 3478 -> 2734 lines.

Optimization round 3c (maintainability).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKdy2R9mbVdE7WNucVwYfv
* Add antenna-azimuth-webapp: real-time GPS azimuth mapper

A Next.js companion to antenna-azimuth-mapper that adds the one thing a
Claude/Cowork session structurally can't do: live browser GPS. Renders an
interactive Esri satellite map (react-leaflet) with azimuth rays/wedges
computed from navigator.geolocation.watchPosition, and an on-demand
shadow-based obstruction-height estimate.

The geodesic and solar-position math (lib/geometry.ts, lib/solar.ts) is a
1:1 TypeScript port of the CLI's geometry.py/solar.py, cross-checked
against it. Shadow detection has no OpenCV available, so lib/shadow.ts
reimplements the same threshold -> connected-components -> PCA-orientation
heuristic by hand, and runs server-side (app/api/shadow-estimate/route.ts,
Node runtime for `sharp`) since reading tile pixels client-side would hit
canvas CORS tainting.

Pinned to next@15.5.23 rather than the 14.2.x line the other quickstarts
use: 14.2.15 has a critical disclosed vulnerability with no 14.2.x fix,
and 15.5.23 (the maintained 15.x backport) clears it. The two remaining
high-severity advisories are inside Next's own internally-vendored
postcss/sharp copies (build tooling and the unused next/image optimizer,
respectively) — not this app's own direct dependencies or usage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FcK727sEgu9Y93wNqeXisu

* Document the live antenna-azimuth-webapp deployment

Adds the public URL and a note that Vercel turns on Vercel Authentication
for new projects, which has to be disabled for the deployment to be
reachable by anyone not on the owning team.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FcK727sEgu9Y93wNqeXisu

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Optimize shadow detection and map re-rendering

Profiled the shadow-estimate pipeline on a 512x512 crop first: morphology
dominated at 56-62ms of a 75-122ms run, with connected-components second at
up to 51ms. Both were naive implementations, so they were rewritten rather
than tuned:

- Morphology now runs as two separable 1D passes over reused ping-pong
  buffers. A 3x3 rectangular structuring element is separable, so this is
  arithmetically identical to the old 9-neighbour scan while touching a
  third of the memory and allocating nothing per pass.
- Connected components writes flat pixel indices into a single Int32Array
  (which doubles as the BFS queue) and returns ranges into it, instead of
  building an [x, y] tuple array per blob — that was hundreds of thousands
  of small allocations on blob-heavy imagery.
- Grayscale, the Otsu histogram and the threshold comparison are fused into
  two passes, using the same integer luma weights OpenCV applies.

Measured on 512x512: 88.5ms -> 38.7ms (2.3x) on clustered blobs, 48.8ms ->
29.3ms (1.7x) on worst-case speckle. Verified byte-identical observations
against the previous implementation across 10 image/size combinations.

On the client, a high-accuracy geolocation watch reports a position about
once a second and consumer GPS jitters well under a metre, so the map was
re-projecting every ray continuously while standing still. Fixes reported
closer than 0.5m now reuse the previous state object (via the already
exported but until now unused haversineDistanceM), accuracy is rounded to
the metre it is displayed at, and the per-ray geodesy is memoised.

Also scopes the .leaflet-container override under .map-shell. Leaflet's own
stylesheet ships in the dynamically imported map's chunk, which loads after
the layout CSS holding the override; at equal specificity Leaflet's rule was
winning, so the map background came from its #ddd default rather than the
theme token. This is a correctness fix, not a size one — the duplicate
leaflet.css import removed alongside it was already deduplicated by the
bundler in production builds (36,872 -> 36,883 bytes of CSS, a wash).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FcK727sEgu9Y93wNqeXisu

* Add drag-to-reposition for the origin marker

The map had two ways to set the origin (live GPS, typing lat/lon) but no
direct on-map interaction. The origin Marker is now draggable; a dragend
handler reads its new position and feeds it through the same
setManualOrigin path the coordinate inputs already use, so dragging is
just another way to enter manual-override mode — "Use live GPS instead"
reverts it exactly as before. RecenterOnFirstFix only fires once, so a
drag doesn't get snapped back by the auto-recenter effect.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FcK727sEgu9Y93wNqeXisu

---------

Co-authored-by: Claude <noreply@anthropic.com>
The shadow feature was built against a misread requirement. The original
goal was to account for "the distortion caused by the angle the satellite
image is taken from"; that became an obstruction-height estimate, which is
not what it is for. It now serves the correction it was always meant to.

What is and isn't distorted, stated plainly in code and docs because part
of the expectation was a misconception: a satellite basemap and a vector
basemap are both Web Mercator, north-up, on the same tile grid, and bearings
come from lat/lon rather than pixels — so for two points on the ground the
azimuth is identical on either layer, and nothing needs correcting.

What is displaced is anything above the ground. Orthorectification places
terrain correctly from a bare-earth model, but a mast or rooftop is not in
that model: its top images along the slant to the satellite and lands
h·cot(E_sat) away along A_sat+180°. Picking an antenna link's far end by its
mast top — the normal way to pick it — therefore yields a bearing that is
off: ~2° at 500 m for a 30 m object at 30° off-nadir, ~5° at 200 m, nothing
at long range.

lib/relief.ts recovers the missing sensor geometry from the image itself. A
shadow and a lean are the same radial geometry with the sun swapped for the
satellite, so a measured shadow plus the astronomically-computed sun gives
the object's height, and that height plus the object's measured lean gives
E_sat and A_sat. The user marks one upright object's base and apparent top;
every elevated target then reports raw and corrected bearings and the delta.

Verified by vitest (30 specs, new): the calibration round-trips a synthetic
lean back to the satellite geometry that produced it, and the corrections
reproduce the hand-worked 2°-at-500 m and 5°-at-200 m figures and decay to
under 0.2° by 10 km.

Also lands the rest of the agreed upgrade list: session state and shareable
links (lib/persist.ts, encoded in the URL fragment so a position never
reaches a server log), an SVG export mirroring the CLI's renderer, a bounded
tile cache and adaptive zoom probing so a location Esri doesn't serve at z19
degrades instead of 502-ing, PWA manifest/icons/app-shell worker, retry and
offline handling on the probe, and a facing indicator. Magnetic declination
was deliberately left out — aiming here is visual, by landmark.

Vitest pinned to 4.x: the 2.x line carries a critical advisory. The three
remaining npm audit highs are the pre-existing ones inside Next's own
vendored postcss/sharp, unchanged by this work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FcK727sEgu9Y93wNqeXisu
The live app has been stuck on a pre-#53 build because the only route to
Vercel from an automated session is an MCP server that is approval-gated,
and api.vercel.com is unreachable from the sandbox, so the CLI is not a
fallback either. A GitHub Actions job runs from a host that can reach both.

The workflow mirrors the shape the other workflows here use: push to main,
a paths filter scoped to one quickstart, one job, working-directory set to
that directory. Lint and the Vitest suite gate the deploy, and
`vercel deploy --prebuilt` ships the build those steps ran against rather
than rebuilding remotely.

workflow_dispatch is deliberate: the paths filter means merging the
workflow alone will not trigger it, so the first deploy is a manual run
from the Actions tab.

Deploying to the existing project through the CLI rather than linking the
repository in the dashboard leaves Deployment Protection untouched, so the
already-public URL stays public. The project and team IDs live in the
workflow's env block; they are identifiers rather than credentials, and a
guard on github.repository keeps forks from deploying into them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FcK727sEgu9Y93wNqeXisu
Replaces Esri World Imagery with the Mapy.cz REST API's aerial mapset,
per request — both the on-screen Leaflet layer and the server-side tile
fetch that feeds shadow detection and the SVG export. Python
antenna-azimuth-mapper is untouched and keeps Esri.

lib/mapycz.ts resolves the tile URL template, attribution, and zoom range
from the aerial mapset's tiles.json at runtime rather than hardcoding a
guessed literal URL: api.mapy.cz's own documentation was unreachable from
this environment (confirmed egress-blocked, same as server.arcgisonline.com
earlier), so the design leans on tiles.json being what the provider's docs
describe it as being for, with defensive parsing and fallback defaults if
the real response shape differs from what search-engine snippets of the
docs describe.

The client never sees the Mapy.cz URL or API key: AzimuthMap.tsx's
TileLayer points at this app's own /api/basemap/[z]/[x]/[y] passthrough
proxy (edge runtime, validates z/x/y before building the outbound request,
wraps x the same way fetchSnapshot already does), and a separate
/api/basemap-meta route supplies just the attribution text, since Mapy.cz's
docs say that text can change and it shouldn't be hand-copied. Leaflet has
no live setter for TileLayer's attribution/maxZoom, so AzimuthMap.tsx keys
the layer on whether the real metadata has loaded to force a clean remount
once it has, rather than silently no-opping on the prop change.

lib/tiles.ts keeps its LRU cache and mosaic/crop math (both already
provider-agnostic Web Mercator), swapping only the tile source and seeding
deepestAvailableZoom's probe from the tileset's own reported max zoom.
chooseZoomForSpan's ceiling moves from Esri's practical 19 to Mapy.cz's
documented Czech Republic ceiling of 20.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FcK727sEgu9Y93wNqeXisu
A Next.js chat app that turns an elite VIP wedding coordinator persona
prompt into a working demo: a parameter intake form (date, venue,
budget, guest count, style, priorities) fills the system prompt, and a
streaming chat interface renders Claude's plan with Markdown tables for
the budget breakdown and day-of timeline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L1zyDH8YM34SYKmdUPQojR
Packages a system-instructions generator as a skill with a JSON
intermediate representation as the source of truth, so retargeting a
profile to another platform or budget is a recompile rather than a
repeated analysis.

The compiler counts characters exactly and drops whole rules ascending
by evidence x impact when over budget, never truncating sentences. The
linter mechanizes the self-check that a model reviewing its own output
tends to wave through: provenance leaks, undocumented rules, bare
prohibitions, portability violations and budget overflow.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ea33xyGvXxKX19nnoWojzW
…-version-hulfzq

Add masterprompt skill for generating System Instructions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants