<?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>🔒 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;">🔒 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) ?>&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 & unique visitors (owner excluded)">
👁 <?= number_format($hits['total']) ?>
· <?= 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">⇓ Import</a>
<?php endif; ?>
<a href="export.php?format=csv&owner=<?= h($owner) ?>&id=<?= h($list_id) ?>"
class="btn btn-sm" title="Download as CSV">💾 CSV</a>
<a href="export.php?format=md&owner=<?= h($owner) ?>&id=<?= h($list_id) ?>"
class="btn btn-sm" title="Download as Markdown">💾 MD</a>
<a href="print.php?id=<?= h($list_id) ?>&owner=<?= h($owner) ?>"
class="btn btn-sm" target="_blank" title="Print view">🖶 Print</a>
<a href="dashboard.php" class="btn btn-sm">←</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>■ 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) ?>&id=<?= h($list_id) ?>&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">✗</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 →</button>
</form>
<a href="<?= h(list_url($owner, $list_id)) ?>" class="btn btn-icon" title="Cancel">✗</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">✓ Save</button>
<a href="<?= h(list_url($owner, $list_id)) ?>" class="btn btn-sm">✗</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']))) ?>">
⏱ <?= h(date('d M Y H:i', strtotime($task['due']))) ?>
<?php if ($ds['label'] !== ''): ?>
— <?= 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>
· <?= 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">▲</button>
</form>
<?php else: ?>
<span class="btn btn-icon" style="color:var(--chrome-mid);cursor:default;">▲</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">▼</button>
</form>
<?php else: ?>
<span class="btn btn-icon" style="color:var(--chrome-mid);cursor:default;">▼</span>
<?php endif; ?>
<!-- Edit -->
<a href="<?= h(list_url($owner, $list_id, ['edit_task' => $task['id']])) ?>"
class="btn btn-icon" title="Edit task">✎</a>
<!-- Move to another list -->
<a href="list.php?owner=<?= h($owner) ?>&id=<?= h($list_id) ?>&move_task=<?= h($task['id']) ?>"
class="btn btn-icon" title="Move to another list">⇄</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">✓</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">✗</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>✓ 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) ?>&id=<?= h($list_id) ?>&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'] ?? '')) ?>
· 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">↩</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">✗</button>
</form>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div><!-- /list-panels -->
</div><!-- /shell-body -->
<?php require_once 'includes/footer.php'; ?>