{# Vendor assets via asset() helper. Default mode = CDN (jsDelivr) with SRI; #} {# when `ntasker assets fetch` ran and `assets_mode=local|auto`, they come from #} {# the user-data dir under /static/vendor instead. SRI is emitted in both modes #} {# (local mode: tamper detection). `crossorigin` is only required for CDN loading. #} {% for tpl in plugin_slots['head'] %}{% include tpl %}{% endfor %} {# Frontend i18n bridge: js_strings is built server-side per request. #} {# Alpine magic property `$i18n('key')` is registered just below. #} {# Sidebar section heading: a button that folds the section's body. #} {# `key` is a name in settings.SIDEBAR_SECTIONS; the open state lives in #} {# `sectionOpen` and is persisted to the `sidebar_sections` setting. #} {% macro section_toggle(key, icon, label) %} {% endmacro %} {# One sidebar project row. `p` is the Alpine expression yielding the #} {# project ({name, label, open_count}); the same markup serves flat rows, #} {# family heads and indented children. `after_badge` is extra markup that #} {# follows the open-count badge (the folded-family pill on head rows). #} {% macro project_row(p, after_badge='') %} {% endmacro %} {# Folded-family pill on a head row: how many children hide behind the #} {# chevron; lit up when one of them is part of the active filter. #} {% macro fold_pill() %} {% endmacro %}
{# Sidebar: project + tag filter (full-width checkboxes). Hidden on mobile (<768px). #} {# Sticky-positioned aside outside the .page wrapper. Tabler/Bootstrap surface vars #} {# drive the bg so dark-mode behaves. Sidebar has its own scroll context; the main #} {# page-wrapper scrolls independently. #} {# Drag handle on the sidebar's edge: resizes the sidebar, width persists in localStorage. #}
{# Main content (full-width inside its column) #}
{# Tabler page-header: title left, view-mode switcher right. #} {# The two pseudo-tabs (Task list / Kanban) drive ``viewMode`` -- #} {# they are NOT bootstrap nav-tabs because the page wrappers below #} {# swap their entire layout, not just a tab-pane. #}
{# v2.0: projects_dir-Banner is gone -- projects emerge #} {# implicitly from tasks; no filesystem setup required. #} {# New-task form. The header doubles as an accordion toggle and #} {# the body starts collapsed, so more of the task list stays #} {# visible until the user actually wants to add something. #}

{{ t('New task') }}

{# Free-form text + AlpineJS combobox: pick an existing project #} {# (Tab/Enter accept the highlighted suggestion, like tags) or #} {# type a new name -- created implicitly when the task is saved. #} {# Empty = cross-project. #}
{# Live match indicator: green check on an exact existing-project #} {# match, a "new folder" icon on a name that does not exist yet #} {# (it will be created on first run -- see message below). #}
{# New-project notice: shown the instant a name that does not exist #} {# yet is typed. Tells the user a directory will be created on run. #}
{# An image pasted here becomes a context attachment (task_context plugin). #}
{% for tpl in plugin_slots['task_form'] %}{% include tpl %}{% endfor %}
{# Tag-Input: AlpineJS-driven combobox. Tags as badges, free-text input, autocomplete dropdown. #}
{# Dependency input: autocomplete by title/#id, picks resolve to #id chips. #}
{# Directory-lock chips: extra projects whose directories this run holds. #}
{# Create + immediately hand the task to its agent -- only when that agent's CLI + a PTY are available. #}
{# ===== Task queue ===== #} {# The auto-run worklist -- and the only way a session starts. #} {# Every run button puts its task at the head of this queue; #} {# ntasker works it through: one running task per project, next #} {# one up as soon as the previous session ends. Dragging inside #} {# the panel reorders it; the header button pauses / resumes. #} {# The rail down the left side marches while the queue runs and #} {# freezes when it is paused -- that is the state indicator. #} {# The whole panel is a drop target for a board card (appends it). #}
{# Solid only when paused with work waiting -- that is the #} {# one state where pressing it changes something visible. #}
{# The empty state says how tasks get in, so it stays visible. #}
{# One column per project -- the worker runs one task per #} {# project at a time, so a column is literally one execution #} {# lane. Cross-project first, then alphabetical. #}
{# Tabs -- list view only. Kanban renders its own column layout below. #} {# Task list #}

{{ t('No tasks') }}

{# ===== Kanban board view ===== #} {# 4 columns: 3 phase columns (planned/wip/review) + 1 collapsible #} {# Done column derived from status='done'. Drag a card between #} {# columns -> PATCH phase (or status when crossing into Done). #} {# Same filters (search / sidebar) feed the same task list; #} {# the kanban view just groups it by phase. #}
{# === Claude run view -- full-page; replaces list/kanban while open. #} {# One tab per live session; the runs keep streaming in the background #} {# when you go Back. Each tab owns its own xterm + WebSocket. === #}
{# Header: back, per-session tabs, live status + actions for the active tab. #}
{# Tab strip: one per open session. Click switches (drives the #/run/ hash). #}
{# One xterm host per tab; only the active one is shown. Stable #} {# :id lets _claudeConnect attach the Terminal to the right node. #}
{# Edit modal #} {# Report modal -- the agent's final report, rendered Markdown, read-only. #}
{% for tpl in plugin_slots['modals'] %}{% include tpl %}{% endfor %} {# Toast container (Tabler/Bootstrap-compatible). Toasts injected by app.js. #}
{# xterm.js for the interactive Claude terminal. Loaded eagerly (no defer) so #} {# the Terminal / FitAddon globals exist before app.js uses them at runtime. #} {# Markdown pipeline for the report modal (and the workspace plugin's file #} {# viewer): marked renders, DOMPurify sanitises. Loaded before app.js. #} {# Plugin scripts push their mixins before app.js builds the component. #} {% for tpl in plugin_slots['scripts'] %}{% include tpl %}{% endfor %}