← Kanban results

Kanban

thinkingcap qwen3.6 27b · typescript

Initial view of Kanban

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 52,731 bytes · SHA-256 74e6e7f6c58e
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Kanban Board</title>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; overflow: hidden; }

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, sans-serif;
  background: #f1f5f9;
  color: #1e293b;
}

#app {
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
}

/* ── Header bar ─────────────────────────────────────────────── */
.header-bar {
  padding: 12px 24px;
  background: #ffffff;
  border-bottom: 1px solid #e2e8f0;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-shrink: 0;
}

.filter-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
}

.search-wrap { position: relative; }

.search-input {
  padding: 8px 14px;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  font-size: 14px;
  width: 220px;
  outline: none;
  transition: border-color 0.2s, box-shadow 0.2s;
}

.search-input:focus {
  border-color: #6366f1;
  box-shadow: 0 0 0 3px rgba(99,102,241,.15);
}

.label-filters {
  display: flex;
  gap: 6px;
  align-items: center;
  flex-wrap: wrap;
}

.label-chip {
  padding: 4px 12px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 500;
  border: 2px solid transparent;
  background: #f8fafc;
  color: var(--chip-color);
  cursor: pointer;
  transition: all 0.15s;
}

.label-chip:hover { background: #e2e8f0; }
.label-chip.active {
  border-color: var(--chip-color);
  background: rgba(99,102,241,.08);
}

.clear-filters-btn {
  padding: 4px 10px;
  border-radius: 6px;
  font-size: 12px;
  border: 1px solid #e2e8f0;
  background: #fff;
  cursor: pointer;
  color: #64748b;
}

.clear-filters-btn:hover { background: #fee2e2; color: #ef4444; }

/* ── Board (columns container) ─────────────────────────────── */
.board {
  display: flex;
  gap: 16px;
  padding: 20px 24px;
  overflow-x: auto;
  overflow-y: hidden;
  flex: 1;
  align-items: flex-start;
}

/* ── Column ─────────────────────────────────────────────────── */
.column {
  min-width: 280px;
  max-width: 320px;
  width: 300px;
  background: #e2e8f0;
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 90px);
  transition: box-shadow 0.2s;
}

.column.wip-over-limit {
  background: rgba(239,68,68,.08);
  outline: 2px solid rgba(239,68,68,.3);
}

.column-header {
  padding: 14px 16px 10px;
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: grab;
  user-select: none;
}

.column-title {
  font-weight: 600;
  font-size: 14px;
  flex: 1;
  cursor: pointer;
}

.card-count {
  background: #cbd5e1;
  color: #475569;
  font-size: 12px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 10px;
}

.col-menu-btn {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 18px;
  color: #94a3b8;
  padding: 2px 6px;
  border-radius: 4px;
}

.col-menu-btn:hover { background: rgba(0,0,0,.06); color: #475569; }

/* ── Card list (scrollable) ─────────────────────────────────── */
.card-list {
  flex: 1;
  overflow-y: auto;
  padding: 4px 12px 8px;
}

.empty-column-hint {
  border: 2px dashed #cbd5e1;
  border-radius: 8px;
  padding: 32px 16px;
  text-align: center;
  color: #94a3b8;
  font-size: 13px;
}

/* ── Card ───────────────────────────────────────────────────── */
.card {
  background: #ffffff;
  border-radius: 8px;
  padding: 12px;
  margin-bottom: 8px;
  box-shadow: 0 1px 3px rgba(0,0,0,.06);
  cursor: grab;
  transition: transform 0.15s, box-shadow 0.15s;
}

.card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0,0,0,.1);
}

.card-labels { display: flex; gap: 4px; margin-bottom: 8px; flex-wrap: wrap; }

.card-label-chip {
  font-size: 11px;
  font-weight: 500;
  color: #fff;
  padding: 2px 8px;
  border-radius: 4px;
}

.card-title {
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  margin-bottom: 4px;
}

.card-desc {
  font-size: 12px;
  color: #64748b;
  line-height: 1.4;
}

.card-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 8px;
}

.card-due {
  font-size: 12px;
  color: #94a3b8;
}

.card-menu-btn {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 16px;
  color: #cbd5e1;
  padding: 2px 4px;
  border-radius: 4px;
}

.card-menu-btn:hover { background: rgba(0,0,0,.06); color: #64748b; }

/* ── Add card button ────────────────────────────────────────── */
.add-card-btn {
  margin: 0 12px 12px;
  padding: 8px;
  border: none;
  background: transparent;
  color: #64748b;
  font-size: 13px;
  cursor: pointer;
  border-radius: 6px;
  text-align: left;
}

.add-card-btn:hover { background: rgba(0,0,0,.05); color: #334155; }

/* ── Drag ghost & placeholder ───────────────────────────────── */
.drag-ghost {
  position: fixed;
  z-index: 1000;
  pointer-events: none;
}

.drop-placeholder {
  height: 4px;
  background: rgba(99,102,241,.3);
  border-radius: 4px;
  margin-bottom: 8px;
}

/* ── Inline edit input ──────────────────────────────────────── */
.inline-edit-input {
  font-size: inherit;
  font-weight: inherit;
  padding: 2px 6px;
  border: 1px solid #6366f1;
  border-radius: 4px;
  outline: none;
  width: 100%;
}

/* ── Dropdown menu ──────────────────────────────────────────── */
.dropdown-menu {
  position: fixed;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 4px 20px rgba(0,0,0,.15);
  padding: 6px 0;
  z-index: 999;
  min-width: 180px;
}

.menu-item {
  padding: 8px 16px;
  font-size: 13px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
}

.menu-item:hover { background: #f1f5f9; }
.menu-item.danger { color: #ef4444; }
.menu-item.danger:hover { background: #fef2f2; }

.label-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
}

/* ── Toast notifications ────────────────────────────────────── */
.toast-container {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
}

.toast {
  background: #1e293b;
  color: #fff;
  padding: 10px 20px;
  border-radius: 8px;
  font-size: 14px;
  display: flex;
  align-items: center;
  gap: 16px;
  box-shadow: 0 4px 12px rgba(0,0,0,.2);
  animation: toast-in 0.3s ease-out;
}

.toast-undo-btn {
  background: #fff;
  color: #1e293b;
  border: none;
  padding: 4px 12px;
  border-radius: 4px;
  font-size: 13px;
  cursor: pointer;
  font-weight: 500;
}

.toast-undo-btn:hover { background: #e2e8f0; }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── Scrollbar styling ──────────────────────────────────────── */
.card-list::-webkit-scrollbar { width: 6px; }
.card-list::-webkit-scrollbar-track { background: transparent; }
.card-list::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }

.board::-webkit-scrollbar { height: 8px; }
.board::-webkit-scrollbar-track { background: transparent; }
.board::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }

/* ── Add column button ──────────────────────────────────────── */
.add-column-btn {
  min-width: 280px;
  width: 300px;
  padding: 16px;
  background: rgba(255,255,255,.5);
  border: 2px dashed #cbd5e1;
  border-radius: 12px;
  color: #94a3b8;
  font-size: 14px;
  cursor: pointer;
  text-align: center;
}

.add-column-btn:hover { background: rgba(255,255,255,.8); border-color: #6366f1; color: #6366f1; }
</style>
</head>
<body>
<div id="app"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./rendering":"src/rendering.ts","./dragdrop":"src/dragdrop.ts","./state":"src/state.ts"},"src/state.ts":{"./constants":"src/constants.ts"},"src/rendering.ts":{"./constants":"src/constants.ts","./state":"src/state.ts"},"src/dragdrop.ts":{"./constants":"src/constants.ts","./state":"src/state.ts","./rendering":"src/rendering.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
// ─── Entry Point ─────────────────────────────────────────────────────
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const rendering_1 = require("./rendering");
const dragdrop_1 = require("./dragdrop");
const S = __importStar(require("./state"));
// Expose render for testing
if (typeof window !== "undefined") {
    window.__KANBAN_RENDER = rendering_1.render;
}
function start() {
    // Initial render
    (0, rendering_1.render)();
    // Add column button at end of board
    addColumnButton();
    // Init drag & drop system
    (0, dragdrop_1.initDragDrop)();
}
function addColumnButton() {
    const board = document.querySelector(".board");
    const btn = document.createElement("button");
    btn.className = "add-column-btn";
    btn.textContent = "+ Add column";
    btn.addEventListener("click", () => {
        S.addColumn("New Column");
        (0, rendering_1.render)();
        addColumnButton(); // Re-append after re-render
    });
    board.appendChild(btn);
}
start();
};

// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
// ─── Tunable Constants ───────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.COLORS = exports.DRAG = exports.DEFAULT_COLUMNS = exports.DEFAULT_LABELS = exports.UNDO_TIMEOUT_MS = exports.STORAGE_KEY = void 0;
exports.STORAGE_KEY = "kanban-board-state";
exports.UNDO_TIMEOUT_MS = 5000;
exports.DEFAULT_LABELS = [
    { id: "l-urgent", name: "Urgent", color: "#ef4444" },
    { id: "l-feature", name: "Feature", color: "#3b82f6" },
    { id: "l-bug", name: "Bug", color: "#f97316" },
    { id: "l-enhance", name: "Enhancement", color: "#8b5cf6" },
    { id: "l-docs", name: "Docs", color: "#06b6d4" },
    { id: "l-design", name: "Design", color: "#ec4899" },
];
exports.DEFAULT_COLUMNS = [
    { title: "Backlog", wipLimit: undefined },
    { title: "To Do", wipLimit: undefined },
    { title: "In Progress", wipLimit: undefined },
    { title: "Review", wipLimit: undefined },
    { title: "Done", wipLimit: undefined },
];
exports.DRAG = {
    thresholdPx: 5, // px before drag starts
    placeholderHeight: 4, // gap indicator height in px
    scrollMarginPx: 60, // distance from edge to trigger auto-scroll
    scrollSpeed: 12, // px per frame during auto-scroll
    cardTiltDeg: 3, // tilt angle while dragging
    elevationShadow: "0 8px 30px rgba(0,0,0,.18), 0 2px 8px rgba(0,0,0,.12)",
};
exports.COLORS = {
    bg: "#f1f5f9",
    columnBg: "#e2e8f0",
    cardBg: "#ffffff",
    textPrimary: "#1e293b",
    textSecondary: "#64748b",
    accent: "#6366f1",
    danger: "#ef4444",
    wipOverLimit: "rgba(239,68,68,.15)",
    placeholderBg: "rgba(99,102,241,.12)",
};
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
// ─── Board Model & State Management ──────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.pushUndo = pushUndo;
exports.canUndo = canUndo;
exports.setUndoCallback = setUndoCallback;
exports.performUndo = performUndo;
exports.save = save;
exports.load = load;
exports.getBoard = getBoard;
exports.setBoard = setBoard;
exports.addColumn = addColumn;
exports.renameColumn = renameColumn;
exports.deleteColumn = deleteColumn;
exports.setWipLimit = setWipLimit;
exports.addCard = addCard;
exports.updateCard = updateCard;
exports.deleteCard = deleteCard;
exports.moveCard = moveCard;
exports.reorderColumns = reorderColumns;
exports.setFilterLabels = setFilterLabels;
exports.setSearchQuery = setSearchQuery;
exports.cardMatchesFilters = cardMatchesFilters;
const constants_1 = require("./constants");
let _idCounter = Date.now();
function uid() { return `id-${_idCounter++}`; }
const undoStack = [];
let undoTimer = null;
function pushUndo(label) {
    const snap = deepClone(getBoard());
    undoStack.push({ snapshot: snap, label, timestamp: Date.now() });
    if (undoStack.length > 20)
        undoStack.shift();
}
function canUndo() {
    return undoStack.length > 0 && (Date.now() - undoStack[undoStack.length - 1].timestamp < constants_1.UNDO_TIMEOUT_MS);
}
let _undoCallback = null;
function setUndoCallback(cb) { _undoCallback = cb; }
function performUndo() {
    if (!canUndo())
        return false;
    const entry = undoStack.pop();
    setBoard(entry.snapshot);
    _undoCallback?.();
    return true;
}
// ─── Persistence ─────────────────────────────────────────────────────
function deepClone(o) { return JSON.parse(JSON.stringify(o)); }
function save() {
    try {
        localStorage.setItem(constants_1.STORAGE_KEY, JSON.stringify(getBoard()));
    }
    catch { }
}
function load() {
    try {
        const raw = localStorage.getItem(constants_1.STORAGE_KEY);
        if (!raw)
            return null;
        return JSON.parse(raw);
    }
    catch {
        return null;
    }
}
// ─── State accessors ─────────────────────────────────────────────────
let _state;
function initState() {
    const saved = load();
    if (saved) {
        _state = saved;
    }
    else {
        _state = createDefaultBoard();
    }
}
function getBoard() { return deepClone(_state); }
function setBoard(s) { _state = s; save(); }
function createDefaultBoard() {
    const cards = {};
    const columns = constants_1.DEFAULT_COLUMNS.map(c => ({
        id: uid(),
        title: c.title,
        cardIds: [],
        wipLimit: c.wipLimit,
    }));
    // Seed some demo cards
    const demoCards = [
        { colIdx: 0, title: "Research competitors", desc: "Analyze top 5 competitor products" },
        { colIdx: 1, title: "Design wireframes", desc: "Low-fidelity mockups for main flow", labels: ["l-design"] },
        { colIdx: 1, title: "Set up CI/CD pipeline", desc: "", labels: ["l-feature"] },
        { colIdx: 2, title: "Implement auth flow", desc: "OAuth + session management", labels: ["l-feature"], dueDate: getFutureDate(3) },
        { colIdx: 2, title: "Fix login redirect bug", desc: "Users redirected to 404 after SSO", labels: ["l-bug", "l-urgent"] },
        { colIdx: 3, title: "API documentation", desc: "OpenAPI spec for v2 endpoints", labels: ["l-docs"] },
        { colIdx: 4, title: "Project kickoff meeting", desc: "" },
    ];
    demoCards.forEach(d => {
        const cardId = uid();
        cards[cardId] = { id: cardId, title: d.title, description: d.desc, labelIds: d.labels || [], dueDate: d.dueDate };
        columns[d.colIdx].cardIds.push(cardId);
    });
    return { columns, cards, labels: constants_1.DEFAULT_LABELS, filterLabelIds: [], searchQuery: "" };
}
function getFutureDate(days) {
    const d = new Date();
    d.setDate(d.getDate() + days);
    return d.toISOString().slice(0, 10);
}
// ─── Column CRUD ─────────────────────────────────────────────────────
function addColumn(title) {
    pushUndo("delete column");
    _state.columns.push({ id: uid(), title, cardIds: [], wipLimit: undefined });
    save();
}
function renameColumn(colId, newTitle) {
    const col = _state.columns.find(c => c.id === colId);
    if (col) {
        col.title = newTitle;
        save();
    }
}
function deleteColumn(colId) {
    if (_state.columns.length <= 1)
        return; // keep at least one
    pushUndo("delete column");
    const col = _state.columns.find(c => c.id === colId);
    if (col) {
        Object.keys(_state.cards).filter(k => col.cardIds.includes(k)).forEach(k => delete _state.cards[k]);
    }
    _state.columns = _state.columns.filter(c => c.id !== colId);
    save();
}
function setWipLimit(colId, limit) {
    const col = _state.columns.find(c => c.id === colId);
    if (col) {
        col.wipLimit = limit;
        save();
    }
}
// ─── Card CRUD ───────────────────────────────────────────────────────
function addCard(colId, title, description) {
    pushUndo("delete card");
    const cardId = uid();
    _state.cards[cardId] = { id: cardId, title, description: description || "", labelIds: [] };
    const col = _state.columns.find(c => c.id === colId);
    if (col)
        col.cardIds.push(cardId);
    save();
}
function updateCard(cardId, updates) {
    const card = _state.cards[cardId];
    if (card) {
        Object.assign(card, updates);
        save();
    }
}
function deleteCard(cardId) {
    pushUndo("delete card");
    delete _state.cards[cardId];
    _state.columns.forEach(c => { c.cardIds = c.cardIds.filter(id => id !== cardId); });
    save();
}
// ─── Drag & Drop state mutations ─────────────────────────────────────
function moveCard(cardId, toColId, toIndex) {
    // Remove from source column
    let srcCol;
    _state.columns.forEach(c => {
        const idx = c.cardIds.indexOf(cardId);
        if (idx !== -1) {
            c.cardIds.splice(idx, 1);
            srcCol = c;
        }
    });
    // Insert into target column at index
    const tgtCol = _state.columns.find(c => c.id === toColId);
    if (tgtCol) {
        const insertAt = Math.min(toIndex, tgtCol.cardIds.length);
        tgtCol.cardIds.splice(insertAt, 0, cardId);
    }
    else if (srcCol) {
        // Target column was deleted? Put back at end of source.
        srcCol.cardIds.push(cardId);
    }
    save();
}
function reorderColumns(fromIdx, toIdx) {
    const col = _state.columns.splice(fromIdx, 1)[0];
    if (col)
        _state.columns.splice(Math.min(toIdx, _state.columns.length), 0, col);
    save();
}
// ─── Filters ─────────────────────────────────────────────────────────
function setFilterLabels(ids) {
    _state.filterLabelIds = ids;
    save();
}
function setSearchQuery(q) {
    _state.searchQuery = q;
    save();
}
function cardMatchesFilters(cardId) {
    const card = _state.cards[cardId];
    if (!card)
        return false;
    // Label filter: show only cards that have ALL selected labels (if any selected)
    if (_state.filterLabelIds.length > 0) {
        for (const lid of _state.filterLabelIds) {
            if (!card.labelIds.includes(lid))
                return false;
        }
    }
    // Search filter: title contains query (case-insensitive)
    if (_state.searchQuery.trim()) {
        if (!card.title.toLowerCase().includes(_state.searchQuery.trim().toLowerCase()))
            return false;
    }
    return true;
}
// ─── Init ────────────────────────────────────────────────────────────
initState();
// Expose for testing/debugging
if (typeof window !== "undefined") {
    window.__KANBAN = {
        getBoard, setBoard, moveCard, reorderColumns, addColumn, deleteColumn,
        addCard, deleteCard, updateCard, canUndo, performUndo, save, load,
        cardMatchesFilters, setFilterLabels, setSearchQuery, renameColumn, setWipLimit, pushUndo,
    };
    // Expose render for testing
    window.__KANBAN_RENDER = null; // will be set by main.ts
}
};

// ── module: src/rendering.ts ──
__mods["src/rendering.ts"] = function (exports, require, module) {
"use strict";
// ─── Rendering: Board → DOM ──────────────────────────────────────────
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.render = render;
exports.showToast = showToast;
const constants_1 = require("./constants");
const S = __importStar(require("./state"));
const app = document.getElementById("app");
function render() {
    const board = S.getBoard();
    // Build the full DOM tree from state — single source of truth.
    app.innerHTML = "";
    // ── Header bar ────────────────────────────────────────────────────
    const headerBar = el("div", "header-bar");
    headerBar.appendChild(buildFilterBar(board));
    app.appendChild(headerBar);
    // ── Board (columns container) ─────────────────────────────────────
    const boardEl = el("div", "board");
    for (const col of board.columns) {
        boardEl.appendChild(buildColumn(col, board.cards, board.labels));
    }
    app.appendChild(boardEl);
    // ── Toast container ───────────────────────────────────────────────
    if (!document.getElementById("toast-container")) {
        app.appendChild(el("div", "toast-container", { id: "toast-container" }));
    }
}
// ── Filter bar (search + label filters) ──────────────────────────────
function buildFilterBar(board) {
    const bar = el("div", "filter-bar");
    // Search input
    const searchWrap = el("div", "search-wrap");
    const searchInput = document.createElement("input");
    searchInput.type = "text";
    searchInput.placeholder = "Search cards…";
    searchInput.className = "search-input";
    searchInput.value = board.searchQuery;
    searchInput.addEventListener("input", () => {
        S.setSearchQuery(searchInput.value);
        render();
    });
    searchWrap.appendChild(searchInput);
    bar.appendChild(searchWrap);
    // Label filter chips
    const labelWrap = el("div", "label-filters");
    for (const lbl of board.labels) {
        const chip = document.createElement("button");
        chip.className = `label-chip${board.filterLabelIds.includes(lbl.id) ? " active" : ""}`;
        chip.style.setProperty("--chip-color", lbl.color);
        chip.textContent = lbl.name;
        chip.addEventListener("click", () => {
            const ids = board.filterLabelIds.includes(lbl.id)
                ? board.filterLabelIds.filter(x => x !== lbl.id)
                : [...board.filterLabelIds, lbl.id];
            S.setFilterLabels(ids);
            render();
        });
        labelWrap.appendChild(chip);
    }
    // Clear filters button (only if active)
    if (board.filterLabelIds.length > 0 || board.searchQuery.trim()) {
        const clearBtn = document.createElement("button");
        clearBtn.className = "clear-filters-btn";
        clearBtn.textContent = "✕ Clear";
        clearBtn.addEventListener("click", () => {
            S.setFilterLabels([]);
            S.setSearchQuery("");
            render();
        });
        labelWrap.appendChild(clearBtn);
    }
    bar.appendChild(labelWrap);
    return bar;
}
// ── Column ───────────────────────────────────────────────────────────
function buildColumn(col, cards, labels) {
    const colEl = el("div", "column");
    colEl.dataset.colId = col.id;
    // WIP limit highlight
    if (col.wipLimit !== undefined && col.cardIds.length > col.wipLimit) {
        colEl.classList.add("wip-over-limit");
    }
    // Header
    const header = el("div", "column-header");
    const titleSpan = document.createElement("span");
    titleSpan.className = "column-title";
    titleSpan.textContent = col.title;
    titleSpan.addEventListener("dblclick", () => startEditColumnTitle(col.id, titleSpan));
    header.appendChild(titleSpan);
    // Card count badge
    const badge = el("span", "card-count");
    badge.textContent = String(col.cardIds.length);
    header.appendChild(badge);
    // Column menu button (⋮)
    const menuBtn = document.createElement("button");
    menuBtn.className = "col-menu-btn";
    menuBtn.textContent = "⋮";
    menuBtn.addEventListener("click", (e) => { e.stopPropagation(); showColumnMenu(col.id, menuBtn); });
    header.appendChild(menuBtn);
    colEl.appendChild(header);
    // Card list
    const cardList = el("div", "card-list");
    cardList.dataset.colId = col.id;
    if (col.cardIds.length === 0) {
        // Empty column drop zone hint
        const emptyHint = el("div", "empty-column-hint");
        emptyHint.textContent = "Drop cards here";
        cardList.appendChild(emptyHint);
    }
    else {
        for (const cardId of col.cardIds) {
            const card = cards[cardId];
            if (!card)
                continue;
            if (!S.cardMatchesFilters(cardId))
                continue; // filtered out — skip rendering
            cardList.appendChild(buildCard(card, labels));
        }
    }
    colEl.appendChild(cardList);
    // Add card button
    const addBtn = el("button", "add-card-btn");
    addBtn.textContent = "+ Add card";
    addBtn.addEventListener("click", () => {
        S.addCard(col.id, "New Card");
        render();
        // Auto-focus the new card's title for inline editing
        const firstTitle = cardList.querySelector(".card-title[contenteditable]");
        if (!firstTitle) {
            // Find the last added card and make it editable
            const titles = cardList.querySelectorAll(".card-title");
            if (titles.length > 0) {
                const t = titles[titles.length - 1];
                startEditCardTitle(t);
            }
        }
    });
    colEl.appendChild(addBtn);
    return colEl;
}
// ── Card ─────────────────────────────────────────────────────────────
function buildCard(card, labels) {
    const cardEl = el("div", "card");
    cardEl.dataset.cardId = card.id;
    // Labels row
    if (card.labelIds.length > 0) {
        const labelRow = el("div", "card-labels");
        for (const lid of card.labelIds) {
            const lbl = labels.find(l => l.id === lid);
            if (!lbl)
                continue;
            const chip = document.createElement("span");
            chip.className = "card-label-chip";
            chip.style.backgroundColor = lbl.color;
            chip.textContent = lbl.name;
            labelRow.appendChild(chip);
        }
        cardEl.appendChild(labelRow);
    }
    // Title
    const title = el("div", "card-title");
    title.textContent = card.title;
    title.addEventListener("dblclick", () => startEditCardTitle(title, card.id));
    cardEl.appendChild(title);
    // Description (truncated)
    if (card.description) {
        const desc = el("div", "card-desc");
        desc.textContent = card.description.length > 80 ? card.description.slice(0, 80) + "…" : card.description;
        cardEl.appendChild(desc);
    }
    // Footer: due date + delete button
    const footer = el("div", "card-footer");
    if (card.dueDate) {
        const due = document.createElement("span");
        due.className = "card-due";
        due.textContent = card.dueDate;
        footer.appendChild(due);
    }
    // Card menu button
    const cMenuBtn = document.createElement("button");
    cMenuBtn.className = "card-menu-btn";
    cMenuBtn.textContent = "⋮";
    cMenuBtn.addEventListener("click", (e) => { e.stopPropagation(); showCardMenu(card.id, cMenuBtn); });
    footer.appendChild(cMenuBtn);
    cardEl.appendChild(footer);
    return cardEl;
}
// ── Inline editing helpers ───────────────────────────────────────────
function startEditColumnTitle(colId, titleSpan) {
    const input = document.createElement("input");
    input.type = "text";
    input.value = titleSpan.textContent || "";
    input.className = "inline-edit-input";
    titleSpan.replaceWith(input);
    input.focus();
    input.select();
    const commit = () => {
        if (input.value.trim())
            S.renameColumn(colId, input.value.trim());
        render();
    };
    const cancel = () => { render(); };
    input.addEventListener("keydown", (e) => {
        if (e.key === "Enter") {
            e.preventDefault();
            commit();
        }
        if (e.key === "Escape") {
            e.preventDefault();
            cancel();
        }
    });
    input.addEventListener("blur", commit);
}
function startEditCardTitle(titleEl, cardId) {
    const current = titleEl.textContent || "";
    const input = document.createElement("input");
    input.type = "text";
    input.value = current;
    input.className = "inline-edit-input";
    titleEl.replaceWith(input);
    input.focus();
    input.select();
    const commit = () => {
        if (cardId && input.value.trim())
            S.updateCard(cardId, { title: input.value.trim() });
        render();
    };
    const cancel = () => { render(); };
    input.addEventListener("keydown", (e) => {
        if (e.key === "Enter") {
            e.preventDefault();
            commit();
        }
        if (e.key === "Escape") {
            e.preventDefault();
            cancel();
        }
    });
    input.addEventListener("blur", commit);
}
// ── Column menu ──────────────────────────────────────────────────────
function showColumnMenu(colId, btn) {
    removeMenus();
    const board = S.getBoard();
    const col = board.columns.find(c => c.id === colId);
    if (!col)
        return;
    const menu = el("div", "dropdown-menu");
    menu.style.position = "fixed";
    const rect = btn.getBoundingClientRect();
    menu.style.top = (rect.bottom + 4) + "px";
    menu.style.left = rect.left + "px";
    // WIP limit item
    const wipItem = el("div", "menu-item");
    wipItem.textContent = col.wipLimit !== undefined ? `WIP Limit: ${col.wipLimit}` : "No WIP Limit";
    wipItem.addEventListener("click", () => {
        removeMenus();
        if (col.wipLimit !== undefined)
            S.setWipLimit(colId, undefined);
        else
            S.setWipLimit(colId, col.cardIds.length + 2);
        render();
    });
    menu.appendChild(wipItem);
    // Rename item
    const renameItem = el("div", "menu-item");
    renameItem.textContent = "Rename";
    renameItem.addEventListener("click", () => {
        removeMenus();
        const titleSpan = document.querySelector(`[data-col-id="${colId}"] .column-title`);
        if (titleSpan)
            startEditColumnTitle(colId, titleSpan);
    });
    menu.appendChild(renameItem);
    // Delete item
    if (board.columns.length > 1) {
        const delItem = el("div", "menu-item danger");
        delItem.textContent = "Delete column";
        delItem.addEventListener("click", () => {
            removeMenus();
            S.deleteColumn(colId);
            render();
            showToast(`Column "${col.title}" deleted`, "undo");
        });
        menu.appendChild(delItem);
    }
    document.body.appendChild(menu);
}
// ── Card menu ────────────────────────────────────────────────────────
function showCardMenu(cardId, btn) {
    removeMenus();
    const board = S.getBoard();
    const card = board.cards[cardId];
    if (!card)
        return;
    const menu = el("div", "dropdown-menu");
    menu.style.position = "fixed";
    const rect = btn.getBoundingClientRect();
    menu.style.top = (rect.bottom + 4) + "px";
    menu.style.left = rect.left + "px";
    // Label toggles
    for (const lbl of board.labels) {
        const item = el("div", "menu-item");
        const hasLabel = card.labelIds.includes(lbl.id);
        item.innerHTML = `<span class="label-dot" style="background:${lbl.color}"></span> ${hasLabel ? "Remove" : "Add"}: ${lbl.name}`;
        item.addEventListener("click", () => {
            removeMenus();
            const ids = hasLabel ? card.labelIds.filter(x => x !== lbl.id) : [...card.labelIds, lbl.id];
            S.updateCard(cardId, { labelIds: ids });
            render();
        });
        menu.appendChild(item);
    }
    // Due date toggle
    const dueItem = el("div", "menu-item");
    dueItem.textContent = card.dueDate ? `Due: ${card.dueDate}` : "Set due date";
    dueItem.addEventListener("click", () => {
        removeMenus();
        if (card.dueDate)
            S.updateCard(cardId, { dueDate: undefined });
        else {
            const d = new Date();
            d.setDate(d.getDate() + 7);
            S.updateCard(cardId, { dueDate: d.toISOString().slice(0, 10) });
        }
        render();
    });
    menu.appendChild(dueItem);
    // Delete
    const delItem = el("div", "menu-item danger");
    delItem.textContent = "Delete card";
    delItem.addEventListener("click", () => {
        removeMenus();
        S.deleteCard(cardId);
        render();
        showToast(`Card "${card.title}" deleted`, "undo");
    });
    menu.appendChild(delItem);
    document.body.appendChild(menu);
}
function removeMenus() {
    document.querySelectorAll(".dropdown-menu").forEach(m => m.remove());
}
// Close menus on outside click
document.addEventListener("click", (e) => {
    if (!e.target.closest(".dropdown-menu") && !e.target.closest("[class$='-menu-btn']")) {
        removeMenus();
    }
});
// ── Toast notifications ──────────────────────────────────────────────
function showToast(message, type) {
    const container = document.getElementById("toast-container");
    if (!container)
        return;
    const toast = el("div", `toast toast-${type || "info"}`);
    toast.textContent = message;
    if (type === "undo") {
        const undoBtn = document.createElement("button");
        undoBtn.className = "toast-undo-btn";
        undoBtn.textContent = "Undo";
        undoBtn.addEventListener("click", () => {
            S.performUndo();
            render();
            toast.remove();
        });
        toast.appendChild(undoBtn);
    }
    container.appendChild(toast);
    setTimeout(() => { if (toast.parentNode)
        toast.remove(); }, constants_1.UNDO_TIMEOUT_MS + 500);
}
// ── Utility ──────────────────────────────────────────────────────────
function el(tag, cls, attrs) {
    const e = document.createElement(tag);
    if (cls)
        e.className = cls;
    if (attrs)
        Object.entries(attrs).forEach(([k, v]) => e.setAttribute(k, v));
    return e;
}
};

// ── module: src/dragdrop.ts ──
__mods["src/dragdrop.ts"] = function (exports, require, module) {
"use strict";
// ─── Drag & Drop System (Pointer Events) ─────────────────────────────
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.initDragDrop = initDragDrop;
const constants_1 = require("./constants");
const S = __importStar(require("./state"));
const rendering_1 = require("./rendering");
let drag = null;
// ─── Card Drag ────────────────────────────────────────────────
function initDragDrop() {
    const app = document.getElementById("app");
    // pointerdown on cards — delegated from app
    app.addEventListener("pointerdown", (e) => {
        if (drag?.started)
            return; // already dragging
        const cardEl = e.target.closest(".card");
        if (!cardEl || e.button !== 0)
            return;
        if (e.target.matches("button"))
            return;
        const cardId = cardEl.dataset.cardId;
        const colEl = cardEl.closest(".column");
        const colId = colEl.dataset.colId;
        const board = S.getBoard();
        const col = board.columns.find(c => c.id === colId);
        const sourceIndex = col.cardIds.indexOf(cardId);
        // Create ghost element (visual clone that follows cursor)
        const ghost = cardEl.cloneNode(true);
        ghost.classList.add("drag-ghost");
        ghost.style.width = cardEl.offsetWidth + "px";
        document.body.appendChild(ghost);
        // Calculate offset from card top-left to pointer
        const rect = cardEl.getBoundingClientRect();
        const offsetX = e.clientX - rect.left;
        const offsetY = e.clientY - rect.top;
        drag = {
            cardId, sourceColId: colId, sourceIndex,
            ghostEl: ghost, placeholder: null,
            offsetX, offsetY, started: false,
            startClientX: e.clientX,
            startClientY: e.clientY,
        };
        // Hide original card (we'll show a placeholder instead)
        cardEl.style.opacity = "0.3";
        cardEl.style.transition = "opacity 0.15s";
    });
    // pointermove — listen on document to catch events regardless of target
    document.addEventListener("pointermove", (e) => {
        if (!drag)
            return;
        // Check threshold before starting drag
        if (!drag.started) {
            const dx = Math.abs(e.clientX - drag.startClientX);
            const dy = Math.abs(e.clientY - drag.startClientY);
            if (dx < constants_1.DRAG.thresholdPx && dy < constants_1.DRAG.thresholdPx)
                return;
            drag.started = true;
            // Add elevation and tilt to ghost
            drag.ghostEl.style.boxShadow = constants_1.DRAG.elevationShadow;
            drag.ghostEl.style.transform = `rotate(${constants_1.DRAG.cardTiltDeg}deg)`;
            drag.ghostEl.style.transition = "none";
            // Insert placeholder at original position
            const origCard = document.querySelector(`.card[data-card-id="${drag.cardId}"]`);
            if (origCard) {
                const ph = createPlaceholder();
                origCard.parentNode?.insertBefore(ph, origCard.nextSibling);
                drag.placeholder = ph;
            }
            // Remove empty-column hints from columns we're dragging through
            document.querySelectorAll(".empty-column-hint").forEach(h => h.remove());
        }
        if (!drag.started)
            return;
        // Move ghost to follow cursor
        drag.ghostEl.style.position = "fixed";
        drag.ghostEl.style.left = (e.clientX - drag.offsetX) + "px";
        drag.ghostEl.style.top = (e.clientY - drag.offsetY) + "px";
        drag.ghostEl.style.zIndex = "1000";
        // Auto-scroll
        handleAutoScroll(e.clientX, e.clientY);
        // Update placeholder position based on cursor location
        updateDropTarget(e.clientX, e.clientY);
    });
    // pointerup — listen on document to catch events regardless of target
    document.addEventListener("pointerup", (e) => {
        if (!drag)
            return;
        finishDrag();
    });
    // Escape to cancel
    document.addEventListener("keydown", (e) => {
        if (e.key === "Escape" && drag?.started) {
            cancelDrag();
        }
    });
    // Column header drag for reordering
    app.addEventListener("pointerdown", (e) => {
        const header = e.target.closest(".column-header");
        if (!header || e.button !== 0)
            return;
        if (e.target.matches("button"))
            return;
        const colEl = header.closest(".column");
        const board = S.getBoard();
        let fromIdx = board.columns.findIndex(c => c.id === colEl.dataset.colId);
        if (fromIdx < 0)
            return;
        let colDragActive = false;
        const startClientX = e.clientX;
        const onColMove = (me) => {
            if (!colDragActive && Math.abs(me.clientX - startClientX) > constants_1.DRAG.thresholdPx) {
                colDragActive = true;
                S.pushUndo("reorder columns");
                colEl.style.opacity = "0.5";
                colEl.style.transition = "none";
            }
            if (!colDragActive)
                return;
            // Find which column we're over
            const cols = document.querySelectorAll(".column");
            for (const c of cols) {
                const rect = c.getBoundingClientRect();
                if (me.clientX > rect.left && me.clientX < rect.right) {
                    const toIdx = board.columns.findIndex(bc => bc.id === c.dataset.colId);
                    if (toIdx !== fromIdx && toIdx >= 0) {
                        S.reorderColumns(fromIdx, toIdx);
                        // Re-find our column index after reorder
                        fromIdx = board.columns.findIndex(bc => bc.id === colEl.dataset.colId);
                    }
                }
            }
        };
        const onColUp = () => {
            document.removeEventListener("pointermove", onColMove);
            document.removeEventListener("pointerup", onColUp);
            colEl.style.opacity = "";
            (0, rendering_1.render)();
        };
        document.addEventListener("pointermove", onColMove);
        document.addEventListener("pointerup", onColUp);
    });
}
// ─── Drop target calculation ────────────────────────────────
function updateDropTarget(clientX, clientY) {
    if (!drag)
        return;
    // Find which column we're over
    const cols = document.querySelectorAll(".column");
    let targetColEl = null;
    for (const c of cols) {
        const rect = c.getBoundingClientRect();
        if (clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom) {
            targetColEl = c;
            break;
        }
    }
    if (!targetColEl)
        return;
    const cardList = targetColEl.querySelector(".card-list");
    const visibleCards = cardList.querySelectorAll(".card:not(.drag-ghost)");
    // Find the closest card to the cursor (by vertical distance)
    let insertIndex = visibleCards.length;
    for (let i = 0; i < visibleCards.length; i++) {
        const rect = visibleCards[i].getBoundingClientRect();
        const midY = rect.top + rect.height / 2;
        if (clientY < midY) {
            insertIndex = i;
            break;
        }
    }
    // Move placeholder to the correct position
    if (!drag.placeholder)
        return;
    if (insertIndex === 0 && visibleCards.length > 0) {
        cardList.insertBefore(drag.placeholder, visibleCards[0]);
    }
    else if (insertIndex >= visibleCards.length) {
        cardList.appendChild(drag.placeholder);
    }
    else {
        cardList.insertBefore(drag.placeholder, visibleCards[insertIndex]);
    }
    // Remove empty column hint if present
    const emptyHint = targetColEl.querySelector(".empty-column-hint");
    if (emptyHint)
        emptyHint.remove();
}
function createPlaceholder() {
    const ph = document.createElement("div");
    ph.className = "drop-placeholder";
    ph.style.height = constants_1.DRAG.placeholderHeight + "px";
    ph.style.backgroundColor = "rgba(99,102,241,0.3)";
    ph.style.borderRadius = "4px";
    ph.style.marginBottom = "8px";
    ph.style.transition = "none";
    return ph;
}
// ─── Auto-scroll during drag ────────────────────────────────
function handleAutoScroll(clientX, clientY) {
    const margin = constants_1.DRAG.scrollMarginPx;
    let scrollX = 0, scrollY = 0;
    // Horizontal board scroll
    if (clientX < margin)
        scrollX = -constants_1.DRAG.scrollSpeed;
    else if (clientX > window.innerWidth - margin)
        scrollX = constants_1.DRAG.scrollSpeed;
    // Vertical column scroll — find the column under cursor
    const cols = document.querySelectorAll(".column");
    for (const c of cols) {
        const rect = c.getBoundingClientRect();
        if (clientX >= rect.left && clientX <= rect.right) {
            const cardList = c.querySelector(".card-list");
            const listRect = cardList.getBoundingClientRect();
            if (clientY < listRect.top + margin)
                scrollY = -constants_1.DRAG.scrollSpeed;
            else if (clientY > listRect.bottom - margin)
                scrollY = constants_1.DRAG.scrollSpeed;
            break;
        }
    }
    if (scrollX || scrollY) {
        window.scrollBy(scrollX, scrollY);
        const board = document.querySelector(".board");
        if (board && scrollX) {
            board.scrollLeft += scrollX;
        }
    }
}
// ─── Finish / Cancel drag ──────────────────────────────────
function finishDrag() {
    if (!drag)
        return;
    const saved = { ...drag }; // snapshot before cleanup
    drag = null; // clear immediately to prevent re-entry
    let targetColId = saved.sourceColId;
    let targetIndex = saved.sourceIndex;
    if (saved.placeholder) {
        const parentList = saved.placeholder.parentNode;
        if (parentList && parentList.classList.contains("card-list")) {
            targetColId = parentList.dataset.colId || saved.sourceColId;
            // Index among visible cards at placeholder position
            const siblings = Array.from(parentList.querySelectorAll(".card:not(.drag-ghost)"));
            targetIndex = siblings.indexOf(saved.placeholder);
            if (targetIndex < 0)
                targetIndex = siblings.length;
        }
        // Remove placeholder from DOM
        saved.placeholder.remove();
    }
    // Remove ghost from DOM
    saved.ghostEl.remove();
    // Move card in state
    S.moveCard(saved.cardId, targetColId, targetIndex);
    (0, rendering_1.render)();
}
function cancelDrag() {
    if (!drag)
        return;
    const saved = { ...drag };
    drag = null;
    // Remove ghost and placeholder
    saved.ghostEl.remove();
    if (saved.placeholder)
        saved.placeholder.remove();
    (0, rendering_1.render)();
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingcap-qwen3.6-27b","provider":"lmstudio","persona":"typescript","sessionId":"39eb2864-a6c7-4cf0-a677-c8927eced7e2","tokensIn":2377655,"tokensOut":43523,"tokensTotal":2421178,"cost":0,"turns":65,"toolCalls":65,"failedToolCalls":2,"timestamp":"2026-07-31T21:58:11.519Z"} -->