diff --git a/src/second_brain/web/app.py b/src/second_brain/web/app.py
index 350a2aa..b6eff16 100644
--- a/src/second_brain/web/app.py
+++ b/src/second_brain/web/app.py
@@ -141,32 +141,41 @@ async def source_detail(request: Request, source_id: int):
)
+_IN_FLIGHT_STATUSES = [
+ SourceStatus.PENDING,
+ SourceStatus.PULLED,
+ SourceStatus.TRANSCRIBED,
+]
+
+
+def _build_queue_context(
+ sess,
+ *,
+ add_flash: Optional[str] = None,
+ add_error: Optional[str] = None,
+) -> dict:
+ """Context for queue.html / _queue_body.html (in-flight source list + form)."""
+ sources = (
+ sess.query(Source)
+ .filter(Source.status.in_(_IN_FLIGHT_STATUSES))
+ .order_by(Source.ingested_at.asc())
+ .all()
+ )
+ return {
+ "sources": [_source_to_dict(s) for s in sources],
+ "domains": DOMAINS,
+ "add_flash": add_flash,
+ "add_error": add_error,
+ }
+
+
@app.get("/queue", response_class=HTMLResponse)
async def queue_view(request: Request):
- """Queue view: pending and in-flight sources."""
+ """Queue view: pending and in-flight sources, with an add-to-queue form."""
db = get_database()
- in_flight_statuses = [SourceStatus.PENDING, SourceStatus.PULLED, SourceStatus.TRANSCRIBED]
with db.session() as sess:
- sources = (
- sess.query(Source)
- .filter(Source.status.in_(in_flight_statuses))
- .order_by(Source.ingested_at.asc())
- .all()
- )
- source_list = [_source_to_dict(s) for s in sources]
-
- return templates.TemplateResponse(
- request,
- "index.html",
- {
- "sources": source_list,
- "domains": DOMAINS,
- "statuses": [s.value for s in SourceStatus],
- "selected_domain": "",
- "selected_status": "",
- "page_title": "Queue",
- },
- )
+ ctx = _build_queue_context(sess)
+ return templates.TemplateResponse(request, "queue.html", ctx)
# ---------------------------------------------------------------------------
@@ -423,12 +432,31 @@ async def sources_add(
except ValueError as exc:
add_error = str(exc)
- with db.session() as sess:
- ctx = _build_dashboard_context(
- sess, add_flash=add_flash, add_error=add_error
- )
+ # Dispatch the response partial based on which page submitted the form.
+ # HTMX always sends HX-Current-URL with the request URL of the user's
+ # current page; we parse the path and pick the matching body partial.
+ # Same /sources/add endpoint, same service call — only the rendered
+ # region differs, so each page refreshes its own list/counts inline.
+ from urllib.parse import urlparse as _urlparse
- return templates.TemplateResponse(request, "_dashboard_body.html", ctx)
+ hx_url = request.headers.get("HX-Current-URL") or request.headers.get(
+ "Referer", ""
+ )
+ source_path = _urlparse(hx_url).path if hx_url else ""
+
+ with db.session() as sess:
+ if source_path.rstrip("/") == "/queue":
+ ctx = _build_queue_context(
+ sess, add_flash=add_flash, add_error=add_error
+ )
+ template_name = "_queue_body.html"
+ else:
+ ctx = _build_dashboard_context(
+ sess, add_flash=add_flash, add_error=add_error
+ )
+ template_name = "_dashboard_body.html"
+
+ return templates.TemplateResponse(request, template_name, ctx)
# ---------------------------------------------------------------------------
diff --git a/src/second_brain/web/templates/_add_to_queue_form.html b/src/second_brain/web/templates/_add_to_queue_form.html
new file mode 100644
index 0000000..126ed3d
--- /dev/null
+++ b/src/second_brain/web/templates/_add_to_queue_form.html
@@ -0,0 +1,67 @@
+{# Add-to-queue form. Reused by /dashboard and /queue — the only
+ difference between the two callers is the HTMX swap target, which the
+ parent template sets via `{% set swap_target = "#…-body" %}` before
+ including this partial. POST goes to /sources/add either way; the
+ route inspects HX-Current-URL to pick the right body partial in its
+ response. #}
+ video on a known host (YouTube / Vimeo) → tower; otherwise treated as an article. YouTube /playlist?list=… fans out.
+ Add to queue
+
+
video on a known host (YouTube / Vimeo) → tower; otherwise treated as an article
-{{ source.url }}
+ {% if source.focus %} +Focus: {{ source.focus }}
+ {% endif %} +Queue is empty.
+Add a URL above to get started.
+