Deep-link "Open in rTasks" to backlog task with banner

- "Open in rTasks" button passes ?backlog=TASK-ID query param
- Kanban component reads param and shows indigo banner with task ID
- Dismiss button removes banner and cleans URL

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-16 13:41:34 -07:00
parent cf296a3bb3
commit 0afde7547c
2 changed files with 19 additions and 1 deletions

View File

@ -27,6 +27,7 @@ class FolkTasksBoard extends HTMLElement {
private priorities = ["LOW", "MEDIUM", "HIGH", "URGENT"]; private priorities = ["LOW", "MEDIUM", "HIGH", "URGENT"];
private _offlineUnsubs: (() => void)[] = []; private _offlineUnsubs: (() => void)[] = [];
private _history = new ViewHistory<"list" | "board">("list"); private _history = new ViewHistory<"list" | "board">("list");
private _backlogTaskId: string | null = null;
private _tour!: TourEngine; private _tour!: TourEngine;
private static readonly TOUR_STEPS = [ private static readonly TOUR_STEPS = [
{ target: '.workspace-card', title: "Workspaces", message: "Select a workspace to open its kanban board.", advanceOnClick: true }, { target: '.workspace-card', title: "Workspaces", message: "Select a workspace to open its kanban board.", advanceOnClick: true },
@ -48,6 +49,9 @@ class FolkTasksBoard extends HTMLElement {
connectedCallback() { connectedCallback() {
this.space = this.getAttribute("space") || "demo"; this.space = this.getAttribute("space") || "demo";
// Check for ?backlog= deep-link from email checklist
const params = new URLSearchParams(window.location.search);
this._backlogTaskId = params.get("backlog");
if (this.space === "demo") { this.loadDemoData(); } if (this.space === "demo") { this.loadDemoData(); }
else { else {
this.subscribeOffline(); this.subscribeOffline();
@ -322,6 +326,9 @@ class FolkTasksBoard extends HTMLElement {
width: 100%; padding: 4px 6px; border-radius: 4px; border: 1px solid var(--rs-primary-hover); width: 100%; padding: 4px 6px; border-radius: 4px; border: 1px solid var(--rs-primary-hover);
background: var(--rs-bg-surface-sunken); color: var(--rs-text-primary); font-size: 13px; font-weight: 500; outline: none; font-family: inherit; background: var(--rs-bg-surface-sunken); color: var(--rs-text-primary); font-size: 13px; font-weight: 500; outline: none; font-family: inherit;
} }
.backlog-banner { display:flex;align-items:center;justify-content:space-between;background:#6366f1;color:#fff;padding:10px 16px;border-radius:8px;margin-bottom:12px;font-size:14px }
.backlog-dismiss { background:none;border:none;color:#fff;font-size:18px;cursor:pointer;padding:0 4px;opacity:0.8 }
.backlog-dismiss:hover { opacity:1 }
.badge.clickable { cursor: pointer; transition: all 0.15s; padding: 4px 10px; min-height: 28px; } .badge.clickable { cursor: pointer; transition: all 0.15s; padding: 4px 10px; min-height: 28px; }
.badge.clickable:hover, .badge.clickable:active { filter: brightness(1.3); transform: scale(1.1); } .badge.clickable:hover, .badge.clickable:active { filter: brightness(1.3); transform: scale(1.1); }
@ -344,6 +351,10 @@ class FolkTasksBoard extends HTMLElement {
</style> </style>
${this.error ? `<div style="color:var(--rs-error);text-align:center;padding:8px">${this.esc(this.error)}</div>` : ""} ${this.error ? `<div style="color:var(--rs-error);text-align:center;padding:8px">${this.esc(this.error)}</div>` : ""}
${this._backlogTaskId ? `<div class="backlog-banner">
<span>📋 Backlog task: <strong>${this.esc(this._backlogTaskId)}</strong></span>
<button class="backlog-dismiss" data-dismiss-backlog></button>
</div>` : ""}
${this.view === "list" ? this.renderList() : this.renderBoard()} ${this.view === "list" ? this.renderList() : this.renderBoard()}
`; `;
this.attachListeners(); this.attachListeners();
@ -444,6 +455,13 @@ class FolkTasksBoard extends HTMLElement {
} }
private attachListeners() { private attachListeners() {
this.shadow.querySelector("[data-dismiss-backlog]")?.addEventListener("click", () => {
this._backlogTaskId = null;
const url = new URL(window.location.href);
url.searchParams.delete("backlog");
window.history.replaceState({}, "", url.toString());
this.render();
});
this.shadow.querySelector("#btn-tour")?.addEventListener("click", () => this.startTour()); this.shadow.querySelector("#btn-tour")?.addEventListener("click", () => this.startTour());
this.shadow.getElementById("create-ws")?.addEventListener("click", () => this.createWorkspace()); this.shadow.getElementById("create-ws")?.addEventListener("click", () => this.createWorkspace());
this.shadow.getElementById("create-task")?.addEventListener("click", () => { this.shadow.getElementById("create-task")?.addEventListener("click", () => {

View File

@ -84,7 +84,7 @@ export function renderWebPage(
const allDone = criteria.every((c) => c.checked); const allDone = criteria.every((c) => c.checked);
const doneMsg = allDone ? `<div style="text-align:center;padding:24px;font-size:18px;color:${SUCCESS_COLOR};">&#127881; All items complete!</div>` : ""; const doneMsg = allDone ? `<div style="text-align:center;padding:24px;font-size:18px;color:${SUCCESS_COLOR};">&#127881; All items complete!</div>` : "";
const rtasksUrl = `${checklistConfig.baseUrl}/demo/rtasks`; const rtasksUrl = `${checklistConfig.baseUrl}/demo/rtasks?backlog=${encodeURIComponent(taskId)}`;
return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${esc(title)} - rTasks</title><style>${styles} return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${esc(title)} - rTasks</title><style>${styles}
.btn{display:inline-block;padding:10px 20px;border-radius:8px;font-size:14px;font-weight:600;text-decoration:none;margin-top:16px} .btn{display:inline-block;padding:10px 20px;border-radius:8px;font-size:14px;font-weight:600;text-decoration:none;margin-top:16px}