GitGram — list.php — GitGram
TaskGram / main / v1.00 / list.php42,395 B↓ Raw
<?php
declare(strict_types=1);

require_once 'includes/config.php';
require_once 'includes/functions.php';

$cur_user = get_session_user();

// Accept both clean URLs (owner + slug) and legacy URLs (owner + id)
$owner = strtolower(trim($_GET['owner'] ?? $_POST['owner'] ?? ''));
$slug  = trim($_GET['slug'] ?? '');
$list_id = preg_replace('/[^a-f0-9]/', '', $_GET['id'] ?? $_POST['list_id'] ?? '');

if (!$owner) {
    flash('error', 'List not found.');
    redirect('dashboard.php');
}

if ($slug !== '') {
    $list = get_list_by_slug($owner, $slug);
} elseif ($list_id !== '') {
    $list = get_list($owner, $list_id);
} else {
    $list = null;
}

if (!$list) {
    flash('error', 'List not found.');
    redirect('dashboard.php');
}

// Canonical ID for all internal use
$list_id = $list['id'];

// ── Sort preference (persisted in session) ─────────────────────────
session_start_safe();
$sk_a = 'sort_' . $list_id . '_active';
$sk_c = 'sort_' . $list_id . '_completed';
$valid_sorts_active    = ['default','created_asc','created_desc','priority','alpha','order','due_asc','due_desc'];
$valid_sorts_completed = ['completed_desc','completed_asc','created_asc','created_desc','alpha'];

if (isset($_GET['sort_active']) && in_array($_GET['sort_active'], $valid_sorts_active, true)) {
    $_SESSION[$sk_a] = $_GET['sort_active'];
    redirect(list_url($owner, $list_id));
}
if (isset($_GET['sort_completed']) && in_array($_GET['sort_completed'], $valid_sorts_completed, true)) {
    $_SESSION[$sk_c] = $_GET['sort_completed'];
    redirect(list_url($owner, $list_id));
}
$sort_active    = $_SESSION[$sk_a] ?? 'default';
$sort_completed = $_SESSION[$sk_c] ?? 'completed_desc';

// Grant session access via share token if provided in URL
if (!empty($_GET['token']) && check_share_token($list, $_GET['token'])) {
    grant_list_access($list_id);
}

if (!can_view_list($list, $cur_user)) {
    flash('error', 'This list is private.');
    redirect($cur_user ? 'dashboard.php' : 'index.php');
}

/* ── Password gate for public password-protected lists ────────────── */
$needs_password_gate = !empty($list['password_hash']) && !can_edit_list($list, $cur_user) && !has_list_access($list_id);

if ($needs_password_gate) {
    // Handle password form submission
    $gate_error = '';
    $gate_wait  = get_rate_limit_wait($list_id);

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['gate_action'] ?? '') === 'unlock') {
        if (!verify_csrf()) {
            $gate_error = 'Invalid request.';
        } elseif ($gate_wait > 0) {
            $gate_error = 'Too many failed attempts. Please wait ' . ceil($gate_wait / 60) . ' minute(s).';
        } elseif (!verify_captcha($_POST['captcha_answer'] ?? '')) {
            $gate_error = 'Incorrect math answer. Please try again.';
            record_failed_attempt($list_id);
            $gate_wait = get_rate_limit_wait($list_id);
        } elseif (!check_rate_limit($list_id)) {
            $gate_error = 'Too many failed attempts. Please wait ' . ceil(get_rate_limit_wait($list_id) / 60) . ' minute(s).';
        } elseif (!verify_list_password($list, $_POST['list_password'] ?? '')) {
            record_failed_attempt($list_id);
            $gate_wait = get_rate_limit_wait($list_id);
            $gate_error = $gate_wait > 0
                ? 'Too many failed attempts. Locked for ' . ceil($gate_wait / 60) . ' minute(s).'
                : 'Incorrect password. ' . (RATE_LIMIT_MAX - (RATE_LIMIT_MAX - (int)ceil(get_rate_limit_wait($list_id)/RATE_LIMIT_WINDOW))) . ' attempt(s) remaining before lockout.';
        } else {
            // Correct password
            grant_list_access($list_id);
            reset_rate_limit($list_id);
            redirect(list_url($owner, $list_id));
        }
    }

    $captcha_q  = set_captcha();
    $page_title = $list['title'] . ' — Restricted';
    require_once 'includes/header.php';
    ?>
    <div class="page-wrap" style="max-width:440px; margin-top:24px;">
        <div class="window">
            <div class="window-title">
                <span>&#128274; Password Required — <?= h($list['title']) ?></span>
            </div>
            <div class="window-body">
                <?php if ($gate_error): ?>
                    <div class="flash flash-error" style="margin:0 0 10px 0;"><?= h($gate_error) ?></div>
                <?php endif; ?>
                <?php if ($gate_wait > 0): ?>
                    <div class="flash flash-error" style="margin:0 0 10px 0;">
                        Access locked. Try again in <?= ceil($gate_wait / 60) ?> minute(s).
                    </div>
                <?php else: ?>
                    <p style="margin-bottom:12px;font-size:12px;color:var(--chrome-darker);">
                        This public list is password protected. Enter the list password to view it.
                    </p>
                    <form method="post" action="<?= h(list_url($owner, $list_id)) ?>">
                        <?= csrf_field() ?>
                        <input type="hidden" name="gate_action" value="unlock">
                        <div class="form-row">
                            <label for="gp_pw">List Password</label>
                            <input type="password" id="gp_pw" name="list_password"
                                   maxlength="128" required autofocus>
                        </div>
                        <div class="form-row">
                            <label for="gp_cap">What is <?= h($captcha_q) ?>?
                                <span style="font-weight:normal;font-size:11px;">(anti-bot check)</span>
                            </label>
                            <input type="text" id="gp_cap" name="captcha_answer"
                                   maxlength="8" required autocomplete="off"
                                   placeholder="Enter the answer">
                        </div>
                        <div class="sep"></div>
                        <div class="d-flex gap-6">
                            <button type="submit" class="btn btn-primary">Unlock</button>
                            <a href="directory.php" class="btn">Cancel</a>
                        </div>
                    </form>
                <?php endif; ?>
            </div>
        </div>
    </div>
    <?php
    require_once 'includes/footer.php';
    exit;
}

$can_edit = can_edit_list($list, $cur_user);

/* ── Record human hit (GET only, non-owner, after password gate) ── */
if ($_SERVER['REQUEST_METHOD'] === 'GET' && !$can_edit) {
    record_list_hit($list_id);
}

/* ── Handle POST actions ──────────────────────────────────────── */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $can_edit) {
    if (!verify_csrf()) {
        flash('error', 'Invalid request.');
        redirect(list_url($owner, $list_id));
    }

    $action  = $_POST['action'] ?? '';
    $task_id = $_POST['task_id'] ?? '';

    switch ($action) {

        case 'add_task':
            $text = trim($_POST['text'] ?? '');
            $pri  = $_POST['priority'] ?? 'normal';
            $due  = parse_due_input($_POST['due'] ?? '');
            if ($text === '') {
                flash('error', 'Task text cannot be empty.');
            } elseif (add_task($owner, $list_id, $text, $pri, $due)) {
                // silent success
            }
            break;

        case 'complete_task':
            complete_task($owner, $list_id, $task_id);
            break;

        case 'uncomplete_task':
            uncomplete_task($owner, $list_id, $task_id);
            break;

        case 'delete_task':
            $pool = ($_POST['pool'] ?? '') === 'completed' ? 'completed' : 'active';
            delete_task($owner, $list_id, $task_id, $pool);
            break;

        case 'move_task':
            $dir = ($_POST['direction'] ?? '') === 'down' ? 'down' : 'up';
            move_task($owner, $list_id, $task_id, $dir);
            break;

        case 'edit_task_save':
            $text      = trim($_POST['text'] ?? '');
            $pri       = $_POST['priority'] ?? 'normal';
            $order_raw = trim($_POST['order'] ?? '');
            $order_val = ($order_raw !== '') ? max(1, (int)$order_raw) : null;
            // Explicit "clear order" checkbox
            if (isset($_POST['clear_order'])) $order_val = null;
            $due_val   = parse_due_input($_POST['due'] ?? '');
            $clear_due = isset($_POST['clear_due']);
            if ($text === '') {
                flash('error', 'Task text cannot be empty.');
                redirect(list_url($owner, $list_id, ['edit_task' => $task_id]));
            }
            edit_task($owner, $list_id, $task_id, $text, $pri, $order_val, $due_val, $clear_due);
            break;

        case 'move_to_list':
            $to_list_id = preg_replace('/[^a-f0-9]/', '', $_POST['to_list_id'] ?? '');
            if ($to_list_id && move_task_to_list($owner, $list_id, $task_id, $to_list_id)) {
                $to = get_list($owner, $to_list_id);
                flash('success', 'Task moved to "' . ($to['title'] ?? 'list') . '".');
            } else {
                flash('error', 'Could not move task.');
            }
            break;

        case 'clear_completed':
            clear_completed($owner, $list_id);
            flash('success', 'Completed queue cleared.');
            break;

        case 'generate_share_token':
            generate_share_token($owner, $list_id);
            flash('success', 'Share link generated.');
            redirect(list_url($owner, $list_id, ['edit' => '1']));

        case 'revoke_share_token':
            revoke_share_token($owner, $list_id);
            flash('success', 'Share link revoked.');
            redirect(list_url($owner, $list_id, ['edit' => '1']));

        case 'update_list':
            $title = trim($_POST['title'] ?? '');
            $desc  = trim($_POST['description'] ?? '');
            $pub   = isset($_POST['public']) && $_POST['public'] === '1';
            if ($title === '') {
                flash('error', 'Title cannot be empty.');
                redirect(list_url($owner, $list_id, ['edit' => '1']));
            }
            update_list_meta($owner, $list_id, $title, $desc, $pub);
            // Handle list password
            $lp_action = $_POST['list_pw_action'] ?? '';
            if ($lp_action === 'set') {
                $lp = $_POST['list_password_new'] ?? '';
                if ($lp !== '') set_list_password($owner, $list_id, $lp);
            } elseif ($lp_action === 'remove') {
                set_list_password($owner, $list_id, '');
            }
            flash('success', 'List settings saved.');
            break;
    }

    redirect(list_url($owner, $list_id));
}

/* ── Reload after any POST ────────────────────────────────────── */
$list = get_list($owner, $list_id);

$edit_task_id = $_GET['edit_task'] ?? '';
$move_task_id = $_GET['move_task']  ?? '';
$show_settings = isset($_GET['edit']);

// Other lists owned by this user (for the move-to selector)
$other_lists = [];
if ($can_edit && $move_task_id !== '') {
    foreach (get_lists_for_user($owner) as $l) {
        if ($l['id'] !== $list_id) {
            $other_lists[] = $l;
        }
    }
}

$page_title   = $list['title'];
$og_image_url = get_user_avatar_url($owner) ?: get_og_logo_url();
$full_height  = !$show_settings;
$body_class  = 'app-shell';

require_once 'includes/header.php';
?>

<?php if ($show_settings && $can_edit): ?>
<!-- ══════════════════════════════════════════════════════════════
     List settings panel (non-full-height)
════════════════════════════════════════════════════════════════ -->
<div class="page-wrap" style="max-width:600px; margin-top:12px;">
    <div class="window">
        <div class="window-title">
            <span>List Settings — <?= h($list['title']) ?></span>
        </div>
        <div class="window-body">
            <form method="post" action="list.php">
                <?= csrf_field() ?>
                <input type="hidden" name="action"   value="update_list">
                <input type="hidden" name="owner"    value="<?= h($owner) ?>">
                <input type="hidden" name="list_id"  value="<?= h($list_id) ?>">
                <div class="form-row">
                    <label for="s_title">Title</label>
                    <input type="text" id="s_title" name="title"
                           value="<?= h($list['title']) ?>" maxlength="128" required>
                </div>
                <div class="form-row">
                    <label for="s_desc">Description</label>
                    <textarea id="s_desc" name="description" rows="3" maxlength="512"><?= h($list['description'] ?? '') ?></textarea>
                </div>
                <div class="form-row">
                    <div class="checkbox-row">
                        <input type="checkbox" id="s_public" name="public" value="1"
                               <?= ($list['public'] ?? false) ? 'checked' : '' ?>>
                        <label for="s_public">Public list (visible in directory without login)</label>
                    </div>
                </div>

                <div class="sep"></div>
                <div class="section-title">List Password <span style="font-weight:normal;font-size:11px;">(optional — protects public lists from casual viewing)</span></div>

                <?php if (!empty($list['password_hash'])): ?>
                    <div style="margin-bottom:8px; padding:6px 8px; background:#FFFFC0; border:1px solid #999;">
                        <span style="color:#774400; font-weight:bold;">&#128274; Password protection is active.</span>
                    </div>
                    <div class="form-row">
                        <div class="checkbox-row">
                            <input type="checkbox" id="s_rm_pw" name="list_pw_action" value="remove">
                            <label for="s_rm_pw" style="color:var(--high);">Remove password protection</label>
                        </div>
                    </div>
                <?php else: ?>
                    <div class="form-row">
                        <label for="s_new_pw">Set a list password <span style="font-weight:normal;">(leave blank for no password)</span></label>
                        <div class="form-row-inline" style="margin:0;">
                            <div class="stretch">
                                <input type="password" id="s_new_pw" name="list_password_new"
                                       maxlength="128" autocomplete="new-password" placeholder="New list password…">
                            </div>
                        </div>
                        <input type="hidden" name="list_pw_action" value="set">
                    </div>
                <?php endif; ?>

                <div class="sep"></div>
                <div class="d-flex gap-6">
                    <button type="submit" class="btn btn-primary">Save Settings</button>
                    <a href="<?= h(list_url($owner, $list_id)) ?>" class="btn">Cancel</a>
                    <a href="print.php?id=<?= h($list_id) ?>&amp;owner=<?= h($owner) ?>"
                       class="btn" target="_blank">Print / Export</a>
                </div>
            </form>

            <!-- Share link (private lists) -->
            <div class="sep"></div>
            <div class="section-title">Private Share Link
                <span style="font-weight:normal;font-size:11px;">(allows guests to view this list without an account)</span>
            </div>
            <?php
            $share_token = $list['share_token'] ?? '';
            if ($share_token !== ''):
                $share_url = rtrim(site_base_url(), '/') . '/' . list_url($owner, $list_id) . '?token=' . urlencode($share_token);
            ?>
                <div style="margin-bottom:8px; padding:6px 8px; background:#E8F4E8; border:1px solid #6a6; word-break:break-all; font-size:12px;">
                    <strong>Share URL:</strong><br>
                    <span style="font-family:monospace;"><?= h($share_url) ?></span>
                </div>
                <form method="post" action="list.php" style="display:inline;">
                    <?= csrf_field() ?>
                    <input type="hidden" name="action"  value="revoke_share_token">
                    <input type="hidden" name="owner"   value="<?= h($owner) ?>">
                    <input type="hidden" name="list_id" value="<?= h($list_id) ?>">
                    <button type="submit" class="btn btn-danger btn-sm"
                            onclick="return confirm('Revoke share link? Anyone with the old link will lose access.')">
                        Revoke Link
                    </button>
                </form>
                <form method="post" action="list.php" style="display:inline; margin-left:6px;">
                    <?= csrf_field() ?>
                    <input type="hidden" name="action"  value="generate_share_token">
                    <input type="hidden" name="owner"   value="<?= h($owner) ?>">
                    <input type="hidden" name="list_id" value="<?= h($list_id) ?>">
                    <button type="submit" class="btn btn-sm"
                            onclick="return confirm('Regenerate share link? The old link will stop working.')">
                        Regenerate
                    </button>
                </form>
            <?php else: ?>
                <p style="font-size:12px; color:var(--chrome-darker); margin:0 0 8px 0;">
                    No share link active. Generate one to let guests view this list via a secret URL.
                </p>
                <form method="post" action="list.php">
                    <?= csrf_field() ?>
                    <input type="hidden" name="action"  value="generate_share_token">
                    <input type="hidden" name="owner"   value="<?= h($owner) ?>">
                    <input type="hidden" name="list_id" value="<?= h($list_id) ?>">
                    <button type="submit" class="btn btn-sm btn-primary">Generate Share Link</button>
                </form>
            <?php endif; ?>
        </div>
    </div>
</div>

<?php require_once 'includes/footer.php'; ?>
<?php return; ?>
<?php endif; ?>

<!-- ══════════════════════════════════════════════════════════════
     Full-height two-panel list view
════════════════════════════════════════════════════════════════ -->

<!-- Sub-header: list info + actions -->
<div class="shell-head" style="background:var(--chrome); border-bottom:2px solid var(--chrome-darker); padding:4px 8px; display:flex; align-items:center; gap:6px; flex-wrap:wrap;">
    <span style="font-weight:bold; font-size:14px;"><?= h($list['title']) ?></span>
    <?php if ($list['public']): ?>
        <span class="badge badge-pub">PUBLIC</span>
    <?php else: ?>
        <span class="badge badge-priv">PRIVATE</span>
    <?php endif; ?>
    <?php if ($list['description'] ?? ''): ?>
        <span style="color:var(--chrome-darker);font-size:12px;flex:1;min-width:60px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">
            <?= h($list['description']) ?>
        </span>
    <?php else: ?>
        <span style="flex:1;"></span>
    <?php endif; ?>
    <?php if ($can_edit):
        $hits = get_list_hits($list_id); ?>
        <span style="font-size:11px; color:var(--chrome-darker); white-space:nowrap;"
              title="Total views &amp; unique visitors (owner excluded)">
            &#128065; <?= number_format($hits['total']) ?>
            &middot; <?= number_format($hits['unique']) ?> unique
        </span>
        <a href="<?= h(list_url($owner, $list_id, ['edit' => '1'])) ?>" class="btn btn-sm">Settings</a>
        <a href="import.php" class="btn btn-sm" title="Import tasks from CSV or TXT">&#8659; Import</a>
    <?php endif; ?>
    <a href="export.php?format=csv&amp;owner=<?= h($owner) ?>&amp;id=<?= h($list_id) ?>"
       class="btn btn-sm" title="Download as CSV">&#128190; CSV</a>
    <a href="export.php?format=md&amp;owner=<?= h($owner) ?>&amp;id=<?= h($list_id) ?>"
       class="btn btn-sm" title="Download as Markdown">&#128190; MD</a>
    <a href="print.php?id=<?= h($list_id) ?>&amp;owner=<?= h($owner) ?>"
       class="btn btn-sm" target="_blank" title="Print view">&#128438; Print</a>
    <a href="dashboard.php" class="btn btn-sm">&#8592;</a>
</div>

<div class="shell-body">
<div class="list-panels">

    <!-- ══════════════════════════════════════════════
         LEFT PANEL — Active Task Pool
    ══════════════════════════════════════════════ -->
    <?php
    // Apply sorting to tasks for display (does not modify storage)
    $active_display    = sort_tasks($list['active'],    $sort_active,    'active');
    $completed_display = sort_tasks($list['completed'], $sort_completed, 'completed');

    $sort_labels_a = [
        'default'      => 'Insert order',
        'created_asc'  => 'Oldest first',
        'created_desc' => 'Newest first',
        'priority'     => 'By priority',
        'alpha'        => 'A-Z',
        'order'        => 'Custom #',
        'due_asc'      => 'Due: soonest',
        'due_desc'     => 'Due: latest',
    ];
    $sort_labels_c = [
        'completed_desc' => 'Completed: newest',
        'completed_asc'  => 'Completed: oldest',
        'created_asc'    => 'Added: oldest',
        'created_desc'   => 'Added: newest',
        'alpha'          => 'A-Z',
    ];
    ?>
    <div class="list-panel">
        <div class="panel-head">
            <span>&#9632; Active Tasks</span>
            <span style="font-size:11px;opacity:.8;">
                <?= count($list['active']) ?> task<?= count($list['active']) !== 1 ? 's' : '' ?>
            </span>
        </div>

        <!-- Sort bar (active) -->
        <div style="background:var(--chrome-mid); padding:2px 6px; display:flex; align-items:center; gap:4px; flex-wrap:wrap; flex-shrink:0; border-bottom:1px solid var(--chrome-dark);">
            <span style="font-size:10px; color:var(--chrome-darker); white-space:nowrap;">Sort:</span>
            <?php foreach ($sort_labels_a as $val => $label): ?>
                <?php if ($val === $sort_active): ?>
                    <span style="font-size:10px; font-weight:bold; color:var(--title-start); white-space:nowrap; padding:1px 4px; background:var(--chrome-light);"><?= h($label) ?></span>
                <?php else: ?>
                    <a href="list.php?owner=<?= h($owner) ?>&amp;id=<?= h($list_id) ?>&amp;sort_active=<?= h($val) ?>"
                       style="font-size:10px; color:var(--title-start); white-space:nowrap; padding:1px 4px;"><?= h($label) ?></a>
                <?php endif; ?>
            <?php endforeach; ?>
        </div>

        <?php if ($can_edit): ?>
        <!-- Add task form -->
        <div class="panel-toolbar">
            <form method="post" action="list.php">
                <?= csrf_field() ?>
                <input type="hidden" name="action"  value="add_task">
                <input type="hidden" name="owner"   value="<?= h($owner) ?>">
                <input type="hidden" name="list_id" value="<?= h($list_id) ?>">
                <div class="form-row-inline" style="margin-bottom:0;">
                    <div class="stretch">
                        <input type="text" name="text" placeholder="New task…"
                               maxlength="512" required
                               style="font-size:13px;">
                    </div>
                    <div>
                        <select name="priority" style="width:90px;">
                            <option value="normal">Normal</option>
                            <option value="high">High</option>
                            <option value="low">Low</option>
                        </select>
                    </div>
                    <div style="display:flex; align-items:center; gap:4px;">
                        <label style="font-size:11px; margin:0; font-weight:normal; white-space:nowrap; color:var(--chrome-darker);">Due</label>
                        <input type="datetime-local" name="due"
                               style="font-size:12px; width:170px;">
                    </div>
                    <div>
                        <button type="submit" class="btn btn-primary btn-sm">Add</button>
                    </div>
                </div>
            </form>
        </div>
        <?php endif; ?>

        <!-- Task list -->
        <div class="panel-scroll">
            <?php if (empty($active_display)): ?>
                <div style="padding:16px; text-align:center; color:var(--chrome-dark); font-size:12px;">
                    <?= $can_edit ? 'No active tasks. Add one above.' : 'No active tasks.' ?>
                </div>
            <?php else: ?>
                <?php foreach ($active_display as $idx => $task): ?>
                <div class="task-item pri-<?= h($task['priority'] ?? 'normal') ?>">

                    <?php if ($move_task_id === $task['id'] && $can_edit): ?>
                        <!-- Move-to-list form -->
                        <div class="task-text">
                            <span style="color:var(--chrome-dark); font-size:12px;">Move: </span>
                            <strong><?= h($task['text']) ?></strong>
                        </div>
                        <div class="task-actions">
                        <?php if (empty($other_lists)): ?>
                            <span style="font-size:11px; color:var(--chrome-dark);">No other lists.</span>
                            <a href="<?= h(list_url($owner, $list_id)) ?>" class="btn btn-icon">&#10007;</a>
                        <?php else: ?>
                            <form method="post" action="list.php" style="display:flex; gap:4px; align-items:center; flex-wrap:wrap;">
                                <?= csrf_field() ?>
                                <input type="hidden" name="action"  value="move_to_list">
                                <input type="hidden" name="owner"   value="<?= h($owner) ?>">
                                <input type="hidden" name="list_id" value="<?= h($list_id) ?>">
                                <input type="hidden" name="task_id" value="<?= h($task['id']) ?>">
                                <select name="to_list_id" style="max-width:160px; font-size:12px;">
                                    <?php foreach ($other_lists as $ol): ?>
                                        <option value="<?= h($ol['id']) ?>"><?= h($ol['title']) ?></option>
                                    <?php endforeach; ?>
                                </select>
                                <button type="submit" class="btn btn-sm btn-primary">Move &#8594;</button>
                            </form>
                            <a href="<?= h(list_url($owner, $list_id)) ?>" class="btn btn-icon" title="Cancel">&#10007;</a>
                        <?php endif; ?>
                        </div>
                    <?php elseif ($edit_task_id === $task['id'] && $can_edit): ?>
                        <!-- Inline edit form -->
                        <form method="post" action="list.php" style="flex:1; display:flex; gap:4px; flex-wrap:wrap;">
                            <?= csrf_field() ?>
                            <input type="hidden" name="action"  value="edit_task_save">
                            <input type="hidden" name="owner"   value="<?= h($owner) ?>">
                            <input type="hidden" name="list_id" value="<?= h($list_id) ?>">
                            <input type="hidden" name="task_id" value="<?= h($task['id']) ?>">
                            <input type="text" name="text"
                                   value="<?= h($task['text']) ?>"
                                   maxlength="512" required style="flex:1; min-width:80px;">
                            <select name="priority" style="width:80px;">
                                <?php foreach (['high','normal','low'] as $p): ?>
                                    <option value="<?= $p ?>" <?= ($task['priority'] ?? 'normal') === $p ? 'selected' : '' ?>><?= ucfirst($p) ?></option>
                                <?php endforeach; ?>
                            </select>
                            <div style="display:flex; align-items:center; gap:4px; flex-wrap:wrap; margin-top:4px;">
                                <label style="font-size:11px; margin:0; font-weight:normal; white-space:nowrap;">Order #</label>
                                <input type="number" name="order" min="1" max="9999"
                                       value="<?= isset($task['order']) ? (int)$task['order'] : '' ?>"
                                       placeholder="—"
                                       style="width:56px; font-size:12px;">
                                <?php if (isset($task['order'])): ?>
                                <label style="font-size:11px; margin:0; font-weight:normal; cursor:pointer; white-space:nowrap;">
                                    <input type="checkbox" name="clear_order" value="1"> Clear #
                                </label>
                                <?php endif; ?>
                                <label style="font-size:11px; margin:0 0 0 6px; font-weight:normal; white-space:nowrap;">Due</label>
                                <?php
                                    $due_val_edit = '';
                                    if (!empty($task['due'])) {
                                        $due_val_edit = date('Y-m-d\TH:i', strtotime($task['due']));
                                    }
                                ?>
                                <input type="datetime-local" name="due"
                                       value="<?= h($due_val_edit) ?>"
                                       style="font-size:12px; width:170px;">
                                <?php if (!empty($task['due'])): ?>
                                <label style="font-size:11px; margin:0; font-weight:normal; cursor:pointer; white-space:nowrap;">
                                    <input type="checkbox" name="clear_due" value="1"> Clear due
                                </label>
                                <?php endif; ?>
                            </div>
                            <button type="submit" class="btn btn-sm btn-success">&#10003; Save</button>
                            <a href="<?= h(list_url($owner, $list_id)) ?>" class="btn btn-sm">&#10007;</a>
                        </form>
                    <?php else: ?>
                        <!-- Normal display -->
                        <div class="task-text">
                            <?php if (isset($task['order'])): ?>
                                <span style="font-size:10px; color:var(--chrome-dark); font-weight:bold; margin-right:4px;">#<?= (int)$task['order'] ?></span>
                            <?php endif; ?>
                            <?= h($task['text']) ?>
                            <?php if (!empty($task['due'])): $ds = due_status($task['due']); ?>
                                <span class="due-badge <?= h($ds['class']) ?>"
                                      title="Due: <?= h(date('d M Y H:i', strtotime($task['due']))) ?>">
                                    &#9201; <?= h(date('d M Y H:i', strtotime($task['due']))) ?>
                                    <?php if ($ds['label'] !== ''): ?>
                                        &mdash; <?= h($ds['label']) ?>
                                    <?php endif; ?>
                                </span>
                            <?php endif; ?>
                            <div class="task-meta">
                                <span class="pri-label-<?= h($task['priority'] ?? 'normal') ?>"><?= ucfirst($task['priority'] ?? 'normal') ?></span>
                                &middot; <?= date('d M Y H:i', strtotime($task['created'])) ?>
                            </div>
                        </div>
                        <?php if ($can_edit): ?>
                        <div class="task-actions">
                            <!-- Move Up -->
                            <?php if ($idx > 0): ?>
                            <form method="post" action="list.php">
                                <?= csrf_field() ?>
                                <input type="hidden" name="action"    value="move_task">
                                <input type="hidden" name="owner"     value="<?= h($owner) ?>">
                                <input type="hidden" name="list_id"   value="<?= h($list_id) ?>">
                                <input type="hidden" name="task_id"   value="<?= h($task['id']) ?>">
                                <input type="hidden" name="direction" value="up">
                                <button type="submit" class="btn btn-icon" title="Move Up">&#9650;</button>
                            </form>
                            <?php else: ?>
                                <span class="btn btn-icon" style="color:var(--chrome-mid);cursor:default;">&#9650;</span>
                            <?php endif; ?>

                            <!-- Move Down -->
                            <?php if ($idx < count($active_display) - 1): ?>
                            <form method="post" action="list.php">
                                <?= csrf_field() ?>
                                <input type="hidden" name="action"    value="move_task">
                                <input type="hidden" name="owner"     value="<?= h($owner) ?>">
                                <input type="hidden" name="list_id"   value="<?= h($list_id) ?>">
                                <input type="hidden" name="task_id"   value="<?= h($task['id']) ?>">
                                <input type="hidden" name="direction" value="down">
                                <button type="submit" class="btn btn-icon" title="Move Down">&#9660;</button>
                            </form>
                            <?php else: ?>
                                <span class="btn btn-icon" style="color:var(--chrome-mid);cursor:default;">&#9660;</span>
                            <?php endif; ?>

                            <!-- Edit -->
                            <a href="<?= h(list_url($owner, $list_id, ['edit_task' => $task['id']])) ?>"
                               class="btn btn-icon" title="Edit task">&#9998;</a>

                            <!-- Move to another list -->
                            <a href="list.php?owner=<?= h($owner) ?>&amp;id=<?= h($list_id) ?>&amp;move_task=<?= h($task['id']) ?>"
                               class="btn btn-icon" title="Move to another list">&#8644;</a>

                            <!-- Complete -->
                            <form method="post" action="list.php">
                                <?= csrf_field() ?>
                                <input type="hidden" name="action"  value="complete_task">
                                <input type="hidden" name="owner"   value="<?= h($owner) ?>">
                                <input type="hidden" name="list_id" value="<?= h($list_id) ?>">
                                <input type="hidden" name="task_id" value="<?= h($task['id']) ?>">
                                <button type="submit" class="btn btn-icon btn-success" title="Mark Complete">&#10003;</button>
                            </form>

                            <!-- Delete -->
                            <form method="post" action="list.php">
                                <?= csrf_field() ?>
                                <input type="hidden" name="action"  value="delete_task">
                                <input type="hidden" name="owner"   value="<?= h($owner) ?>">
                                <input type="hidden" name="list_id" value="<?= h($list_id) ?>">
                                <input type="hidden" name="task_id" value="<?= h($task['id']) ?>">
                                <input type="hidden" name="pool"    value="active">
                                <button type="submit" class="btn btn-icon btn-danger" title="Delete task">&#10007;</button>
                            </form>
                        </div>
                        <?php endif; ?>
                    <?php endif; ?>

                </div>
                <?php endforeach; ?>
            <?php endif; ?>
        </div>
    </div>

    <!-- ══════════════════════════════════════════════
         RIGHT PANEL — Completion Queue
    ══════════════════════════════════════════════ -->
    <div class="list-panel">
        <div class="panel-head">
            <span>&#10003; Completed Queue</span>
            <span style="font-size:11px;opacity:.8;">
                <?= count($list['completed']) ?> item<?= count($list['completed']) !== 1 ? 's' : '' ?>
            </span>
        </div>

        <!-- Sort bar (completed) -->
        <div style="background:var(--chrome-mid); padding:2px 6px; display:flex; align-items:center; gap:4px; flex-wrap:wrap; flex-shrink:0; border-bottom:1px solid var(--chrome-dark);">
            <span style="font-size:10px; color:var(--chrome-darker); white-space:nowrap;">Sort:</span>
            <?php foreach ($sort_labels_c as $val => $label): ?>
                <?php if ($val === $sort_completed): ?>
                    <span style="font-size:10px; font-weight:bold; color:var(--title-start); white-space:nowrap; padding:1px 4px; background:var(--chrome-light);"><?= h($label) ?></span>
                <?php else: ?>
                    <a href="list.php?owner=<?= h($owner) ?>&amp;id=<?= h($list_id) ?>&amp;sort_completed=<?= h($val) ?>"
                       style="font-size:10px; color:var(--title-start); white-space:nowrap; padding:1px 4px;"><?= h($label) ?></a>
                <?php endif; ?>
            <?php endforeach; ?>
        </div>

        <?php if ($can_edit && !empty($list['completed'])): ?>
        <div class="panel-toolbar">
            <form method="post" action="list.php">
                <?= csrf_field() ?>
                <input type="hidden" name="action"  value="clear_completed">
                <input type="hidden" name="owner"   value="<?= h($owner) ?>">
                <input type="hidden" name="list_id" value="<?= h($list_id) ?>">
                <button type="submit" class="btn btn-sm btn-danger"
                        title="Remove all completed tasks">Clear All Completed</button>
            </form>
        </div>
        <?php endif; ?>

        <div class="panel-scroll">
            <?php if (empty($completed_display)): ?>
                <div style="padding:16px; text-align:center; color:var(--chrome-dark); font-size:12px;">
                    No completed tasks yet.
                </div>
            <?php else: ?>
                <?php foreach ($completed_display as $task): ?>
                <div class="task-item completed-item pri-<?= h($task['priority'] ?? 'normal') ?>">
                    <div class="task-text">
                        <span style="text-decoration:line-through; color:var(--chrome-dark);"><?= h($task['text']) ?></span>
                        <div class="task-meta">
                            Completed <?= date('d M Y H:i', strtotime($task['completed'] ?? '')) ?>
                            &middot; Added <?= date('d M Y', strtotime($task['created'] ?? '')) ?>
                        </div>
                    </div>
                    <?php if ($can_edit): ?>
                    <div class="task-actions">
                        <!-- Restore -->
                        <form method="post" action="list.php">
                            <?= csrf_field() ?>
                            <input type="hidden" name="action"  value="uncomplete_task">
                            <input type="hidden" name="owner"   value="<?= h($owner) ?>">
                            <input type="hidden" name="list_id" value="<?= h($list_id) ?>">
                            <input type="hidden" name="task_id" value="<?= h($task['id']) ?>">
                            <button type="submit" class="btn btn-icon" title="Restore to active">&#8617;</button>
                        </form>
                        <!-- Delete -->
                        <form method="post" action="list.php">
                            <?= csrf_field() ?>
                            <input type="hidden" name="action"  value="delete_task">
                            <input type="hidden" name="owner"   value="<?= h($owner) ?>">
                            <input type="hidden" name="list_id" value="<?= h($list_id) ?>">
                            <input type="hidden" name="task_id" value="<?= h($task['id']) ?>">
                            <input type="hidden" name="pool"    value="completed">
                            <button type="submit" class="btn btn-icon btn-danger" title="Delete">&#10007;</button>
                        </form>
                    </div>
                    <?php endif; ?>
                </div>
                <?php endforeach; ?>
            <?php endif; ?>
        </div>
    </div>

</div><!-- /list-panels -->
</div><!-- /shell-body -->

<?php require_once 'includes/footer.php'; ?>
Ready
GitGram