chronis 0.5.3

Event-sourced task CLI powered by the AllSource embedded database (all-source.xyz)
Documentation
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>chronis — kanban</title>
  <link rel="stylesheet" href="/style.css">
  <script src="/htmx.min.js"></script>
</head>
<body hx-headers='{"Accept": "text/html"}'>
  <div class="container">
    <header>
      <h1>chronis</h1>
      <nav>
        <a href="/">Dashboard</a>
        <a href="/kanban" class="active">Kanban</a>
        <a href="/graph">Graph</a>
      </nav>
      <div class="stats-bar"
           hx-get="/partials/stats"
           hx-trigger="load, every 2s, refresh from:body"
           hx-swap="innerHTML">
      </div>
    </header>

    <div class="toolbar">
      <div class="search-box">
        <input type="text" id="search-input" placeholder="Search cards..."
               oninput="filterCards()">
      </div>
      <a href="/api/export" class="export-btn" download>Export .md</a>
    </div>

    <div style="display: grid; grid-template-columns: 1fr 380px; gap: 16px;">
      <div id="kanban-container"
           hx-get="/partials/kanban"
           hx-trigger="load, every 2s, refresh from:body"
           hx-swap="innerHTML">
      </div>

      <div class="panel" id="detail-pane">
        <p class="empty-state">Click a card to see details</p>
      </div>
    </div>

    <div class="shortcut-bar">
      <span><kbd class="kbd">/</kbd> search</span>
      <span><kbd class="kbd">e</kbd> export</span>
    </div>
  </div>

  <div class="toast" id="toast"></div>

  <script>
    function filterCards() {
      const query = document.getElementById('search-input').value.toLowerCase();
      document.querySelectorAll('.kanban-card').forEach(card => {
        const text = card.textContent.toLowerCase();
        card.style.display = !query || text.includes(query) ? '' : 'none';
      });
    }

    document.addEventListener('keydown', function(e) {
      const searchInput = document.getElementById('search-input');
      if (e.key === '/' && document.activeElement !== searchInput) {
        e.preventDefault();
        searchInput.focus();
        return;
      }
      if (e.key === 'Escape') {
        searchInput.blur();
        searchInput.value = '';
        filterCards();
        return;
      }
      if (document.activeElement === searchInput) return;
      if (e.key === 'e') window.location.href = '/api/export';
    });

    document.body.addEventListener('htmx:afterSwap', function(e) {
      if (e.detail.target.id === 'kanban-container') {
        filterCards();
      }
    });
  </script>
</body>
</html>