docs: add telegram roadmap and refresh share page
This commit is contained in:
163
.taskmaster/docs/prd-telegram-native-app-bot-wallet.md
Normal file
163
.taskmaster/docs/prd-telegram-native-app-bot-wallet.md
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
# PRD: Telegram-Native App, Bot, and Wallet Experience
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Create a separate delivery track for making Amanat usable from inside Telegram through a bot plus Telegram Mini App. The goal is not only notifications, but a native-feeling Telegram surface that can cover buyer, seller, escrow, chat, dispute, payment, release/refund, and support workflows.
|
||||||
|
|
||||||
|
This track should remain separate from the current security remediation and Request Network migration backlog, but it depends on those foundations for any money movement or high-risk actions.
|
||||||
|
|
||||||
|
## Platform assumptions checked May 24, 2026
|
||||||
|
|
||||||
|
- Telegram Mini Apps provide a full JavaScript web interface inside Telegram and can be launched from bot profile, menu button, inline buttons, direct links, inline mode, and selected attachment-menu contexts.
|
||||||
|
- Mini Apps must validate `Telegram.WebApp.initData` on the backend before trusting Telegram user identity. `initDataUnsafe` is not trusted.
|
||||||
|
- Bot API payments support invoices, invoice links, pre-checkout handling, successful payment events, Telegram Stars, and refund/subscription management for Stars.
|
||||||
|
- TON Connect is the standard wallet connection protocol for TON dApps and Telegram Mini Apps. It lets apps request wallet connection, signatures, and transactions without controlling user keys.
|
||||||
|
- TON Pay SDK and Wallet Pay are possible payment paths for TON/jetton/Telegram-wallet-style crypto payments, but they must be evaluated against escrow ledger, webhook, jurisdiction, KYC, fee, refund, and reconciliation requirements before production use.
|
||||||
|
|
||||||
|
Reference docs:
|
||||||
|
|
||||||
|
- Telegram Mini Apps: https://core.telegram.org/bots/webapps
|
||||||
|
- Telegram Bot API payments: https://core.telegram.org/bots/api#payments
|
||||||
|
- TON Connect: https://docs.ton.org/applications/ton-connect/overview
|
||||||
|
- TON Pay SDK: https://docs.ton.org/applications/ton-pay/overview
|
||||||
|
- Wallet Pay API: https://docs.wallet.tg/pay/
|
||||||
|
|
||||||
|
## Product goal
|
||||||
|
|
||||||
|
Users should be able to complete the practical Amanat journey without leaving Telegram:
|
||||||
|
|
||||||
|
- Register or link an Amanat account from Telegram.
|
||||||
|
- Browse and create purchase requests.
|
||||||
|
- Receive seller offers and buyer decisions.
|
||||||
|
- Chat around a request with structured context.
|
||||||
|
- Start escrow payment from Telegram.
|
||||||
|
- See payment, delivery, dispute, and release state.
|
||||||
|
- Submit evidence for delivery or dispute.
|
||||||
|
- Confirm delivery, request refund, or approve release when eligible.
|
||||||
|
- Receive actionable notifications and reminders.
|
||||||
|
- Use wallet/payments options that are safe for escrow accounting.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Do not bypass the canonical backend authorization, funds ledger, or escrow state machine.
|
||||||
|
- Do not treat Telegram chat messages as the source of truth for financial state.
|
||||||
|
- Do not release funds based only on bot callback data or client-side Mini App state.
|
||||||
|
- Do not store Telegram bot tokens, Wallet Pay keys, or TON private keys in frontend code.
|
||||||
|
- Do not make Telegram the only supported channel unless a later product decision explicitly says so.
|
||||||
|
|
||||||
|
## Architecture principles
|
||||||
|
|
||||||
|
- The Mini App is a client surface. The backend remains the authority for identity, authorization, payment state, escrow state, disputes, and audit logs.
|
||||||
|
- Telegram identity maps to an Amanat user through a verified link table keyed by Telegram user ID and Amanat user ID.
|
||||||
|
- Every Telegram-originated command, callback, webhook, and Mini App request is authenticated and rate-limited.
|
||||||
|
- Telegram callbacks must be idempotent and replay-safe.
|
||||||
|
- Payment events flow into the same provider-neutral payment adapter, ledger, and reconciliation pipeline as web checkout.
|
||||||
|
- High-risk actions such as release, refund, payout address change, dispute resolution, and admin override require step-up confirmation or explicit backend policy.
|
||||||
|
|
||||||
|
## Task set
|
||||||
|
|
||||||
|
### Task 1: Define Telegram product surface and flow map
|
||||||
|
|
||||||
|
Document which Amanat workflows live in bot messages, which live in the Mini App, and which remain web/admin-only for the first release.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- Buyer, seller, admin/support, unauthenticated, linked-user, and unlinked-user journeys are mapped.
|
||||||
|
- Deep-link entry points are specified for request details, offer review, payment, dispute, delivery evidence, and account linking.
|
||||||
|
- First-release scope separates must-have flows from later enhancements.
|
||||||
|
- Every Telegram action maps to an existing or planned backend API/state transition.
|
||||||
|
|
||||||
|
### Task 2: Build Telegram identity linking and session model
|
||||||
|
|
||||||
|
Implement secure account linking between Telegram users and Amanat accounts.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- Backend verifies Mini App `initData` before creating a Telegram session.
|
||||||
|
- Telegram user IDs are linked to Amanat users through an auditable association table.
|
||||||
|
- Account linking supports existing users, new users, unlinking, blocked accounts, and duplicate Telegram-account attempts.
|
||||||
|
- Session expiry, replay protection, rate limits, and audit logging are defined.
|
||||||
|
|
||||||
|
### Task 3: Implement bot command and notification foundation
|
||||||
|
|
||||||
|
Create the Telegram bot backend for commands, inline keyboards, callback queries, deep links, and outbound notifications.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- Bot supports start/help/link/status/request/offer/payment/dispute/settings basics.
|
||||||
|
- Callback payloads use short opaque IDs or signed tokens, not raw financial state.
|
||||||
|
- All incoming updates are idempotently processed and rate-limited.
|
||||||
|
- Notification preferences and quiet/error states are respected.
|
||||||
|
- Failed delivery, blocked bot, and retry behavior are observable.
|
||||||
|
|
||||||
|
### Task 4: Build Telegram Mini App shell for core marketplace workflows
|
||||||
|
|
||||||
|
Deliver the mobile-first Mini App that gives users the full Amanat workflow surface inside Telegram.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- Mini App uses Telegram theme, safe-area, viewport, back button, haptics, and main/bottom button patterns.
|
||||||
|
- Users can browse requests, create/edit requests, review offers, view escrow/payment state, upload evidence, and manage dispute/delivery actions.
|
||||||
|
- The Mini App can be launched from bot profile, menu button, inline buttons, and direct links with `startapp` context.
|
||||||
|
- UX handles unlinked accounts, expired sessions, unsupported Telegram clients, and fallback web links.
|
||||||
|
|
||||||
|
### Task 5: Add Telegram payment and wallet strategy
|
||||||
|
|
||||||
|
Evaluate and implement safe payment entry points for Telegram-native users without weakening escrow accounting.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- Compare Bot API payments/Stars, Wallet Pay, TON Pay, TON Connect, Request Network links, and existing crypto checkout for supported use cases.
|
||||||
|
- Select a first payment path and document rejected options.
|
||||||
|
- Payment creation stores provider, Telegram user ID, deep-link source, payment reference, invoice/order/request ID, currency, amount, expiration, and idempotency key.
|
||||||
|
- Wallet/TON flows validate recipient, asset, amount, memo/reference, confirmation status, and reconciliation evidence before crediting escrow.
|
||||||
|
- Refund/release behavior is explicitly compatible with the canonical ledger and dispute holds.
|
||||||
|
|
||||||
|
### Task 6: Expose escrow, delivery, dispute, and release actions safely
|
||||||
|
|
||||||
|
Make Telegram actions useful for real escrow work while preserving backend state authority.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- Telegram users can see current escrow state, next allowed actions, and blockers.
|
||||||
|
- Delivery confirmation, evidence upload, refund request, dispute open/respond, and release approval route through backend precondition checks.
|
||||||
|
- High-risk actions require fresh confirmation and are audit logged with Telegram context.
|
||||||
|
- Disputed or held funds cannot be released through Telegram shortcuts.
|
||||||
|
|
||||||
|
### Task 7: Add admin/support operating surface for Telegram-originated cases
|
||||||
|
|
||||||
|
Give support/admin users enough visibility to handle Telegram-originated users and payments.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- Admin UI/API shows Telegram linked identity, bot notification status, launch source, payment provider, and wallet/payment references.
|
||||||
|
- Support can resend links, revoke Telegram link, block bot access, and inspect Telegram-originated events.
|
||||||
|
- Admin overrides require the same step-up/two-person policy as web flows if configured.
|
||||||
|
|
||||||
|
### Task 8: Security, compliance, and abuse controls for Telegram
|
||||||
|
|
||||||
|
Threat-model the Telegram surface and add controls before launch.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- Threat model covers forged init data, callback replay, deep-link parameter tampering, phishing links, bot token leakage, spam, account takeover, wallet spoofing, fake payment proof, and support impersonation.
|
||||||
|
- Secrets, bot webhook endpoints, Wallet Pay keys, TON Connect manifest, CORS, CSP, allowed origins, and rate limits are documented.
|
||||||
|
- Monitoring covers update processing failures, abnormal callbacks, payment mismatches, blocked notifications, and suspicious wallet activity.
|
||||||
|
|
||||||
|
### Task 9: QA, rollout, analytics, and launch operations
|
||||||
|
|
||||||
|
Prepare the Telegram app/bot for controlled release.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- Test matrix covers Telegram iOS, Android, Desktop, Web, light/dark themes, compact/fullscreen modes, slow network, blocked bot, expired sessions, and payment cancellation.
|
||||||
|
- Sandbox/test bot and production bot environments are separated.
|
||||||
|
- Rollout uses feature flags, internal allowlist, beta cohort, and production enablement.
|
||||||
|
- Analytics measure activation, linked accounts, request creation, offer response, payment start/completion, dispute activity, release approval, and notification opt-outs.
|
||||||
|
- Runbooks exist for bot outage, Telegram API outage, payment provider outage, stuck payment, duplicate callback, suspicious wallet proof, and compromised bot token.
|
||||||
|
|
||||||
|
## Dependency guidance
|
||||||
|
|
||||||
|
- Tasks involving release/refund/payment crediting should depend on the platform hardening and ledger/payment-provider work.
|
||||||
|
- Identity, bot navigation, Mini App shell, and notification preferences can start earlier behind feature flags.
|
||||||
|
- Wallet/TON payment work must not precede the provider-neutral adapter, ledger invariants, and webhook/idempotency contracts.
|
||||||
@@ -6,170 +6,385 @@
|
|||||||
<title>Amanat Taskmaster Queue</title>
|
<title>Amanat Taskmaster Queue</title>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--ink: #172018;
|
--ink: #10100f;
|
||||||
--muted: #667064;
|
--muted: #6f716c;
|
||||||
--paper: #fbf6e8;
|
--paper: #f4f2eb;
|
||||||
--panel: #fffdf5;
|
--panel: #ffffff;
|
||||||
--line: #ded5bd;
|
--soft: #e8e4d8;
|
||||||
--green: #1c7c54;
|
--line: #d8d3c5;
|
||||||
--amber: #b7791f;
|
--green: #169b62;
|
||||||
--red: #b42318;
|
--amber: #c98616;
|
||||||
--blue: #245b84;
|
--red: #d64c3c;
|
||||||
--shadow: 0 18px 60px rgba(39, 32, 18, 0.14);
|
--blue: #3568ff;
|
||||||
|
--black: #10100f;
|
||||||
|
--shadow: 0 20px 60px rgba(16, 16, 15, 0.08);
|
||||||
}
|
}
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
|
html { scroll-behavior: smooth; }
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: ui-serif, Georgia, Cambria, "Times New Roman", serif;
|
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
background:
|
background: var(--paper);
|
||||||
radial-gradient(circle at 15% 10%, rgba(28, 124, 84, 0.18), transparent 28rem),
|
|
||||||
radial-gradient(circle at 90% 5%, rgba(183, 121, 31, 0.16), transparent 24rem),
|
|
||||||
linear-gradient(135deg, #f7efd9 0%, #fbf6e8 48%, #edf4ea 100%);
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
body::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
background:
|
||||||
|
linear-gradient(rgba(16, 16, 15, 0.035) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(16, 16, 15, 0.035) 1px, transparent 1px);
|
||||||
|
background-size: 52px 52px;
|
||||||
|
mask-image: linear-gradient(to bottom, black, transparent 72%);
|
||||||
|
}
|
||||||
header {
|
header {
|
||||||
padding: 48px clamp(18px, 4vw, 64px) 28px;
|
padding: 22px clamp(18px, 4vw, 56px) 30px;
|
||||||
border-bottom: 1px solid rgba(80, 67, 41, 0.18);
|
}
|
||||||
|
.topbar {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto 72px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.brand {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.brand-mark {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border: 2px solid var(--ink);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-grid;
|
||||||
|
place-items: center;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.nav a {
|
||||||
|
color: var(--ink);
|
||||||
|
text-decoration: none;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 9px 13px;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.nav a:hover,
|
||||||
|
.nav .active {
|
||||||
|
background: var(--black);
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
.hero {
|
.hero {
|
||||||
max-width: 1180px;
|
max-width: 1280px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1.3fr) minmax(280px, 0.7fr);
|
grid-template-columns: minmax(0, 1fr) minmax(300px, 420px);
|
||||||
gap: 28px;
|
gap: clamp(28px, 5vw, 72px);
|
||||||
align-items: end;
|
align-items: center;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
font-size: clamp(2.4rem, 6vw, 5.8rem);
|
font-size: clamp(3.2rem, 10vw, 9.6rem);
|
||||||
line-height: 0.9;
|
line-height: 0.82;
|
||||||
letter-spacing: -0.06em;
|
letter-spacing: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
font-weight: 900;
|
||||||
|
max-width: 8ch;
|
||||||
}
|
}
|
||||||
.lead {
|
.lead {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 1.1rem;
|
font-size: clamp(1.02rem, 1.5vw, 1.25rem);
|
||||||
max-width: 760px;
|
max-width: 720px;
|
||||||
line-height: 1.65;
|
line-height: 1.45;
|
||||||
margin: 22px 0 0;
|
margin: 28px 0 0;
|
||||||
}
|
}
|
||||||
.stats {
|
.stats {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
.stat {
|
.stat {
|
||||||
background: rgba(255, 253, 245, 0.78);
|
min-height: 132px;
|
||||||
|
background: var(--panel);
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 22px;
|
border-radius: 8px;
|
||||||
padding: 18px;
|
padding: 18px;
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.stat strong {
|
||||||
|
display: block;
|
||||||
|
font-size: clamp(2.2rem, 4vw, 4.2rem);
|
||||||
|
line-height: 0.85;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
.stat span {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
.stat strong { display: block; font-size: 2.1rem; line-height: 1; }
|
|
||||||
.stat span { color: var(--muted); font-size: 0.9rem; }
|
|
||||||
main {
|
main {
|
||||||
max-width: 1180px;
|
max-width: 1280px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 28px clamp(18px, 4vw, 64px) 64px;
|
padding: 36px clamp(18px, 4vw, 56px) 72px;
|
||||||
}
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 22px;
|
justify-content: space-between;
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
padding: 16px 0;
|
||||||
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
input, select, a.button {
|
input, select, a.button, .status-pill {
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--black);
|
||||||
background: rgba(255, 253, 245, 0.86);
|
background: transparent;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
padding: 12px 16px;
|
padding: 12px 15px;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font: inherit;
|
font: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 750;
|
||||||
}
|
}
|
||||||
input { min-width: min(100%, 360px); flex: 1; }
|
input {
|
||||||
.task {
|
min-width: min(100%, 320px);
|
||||||
background: rgba(255, 253, 245, 0.9);
|
flex: 1 1 320px;
|
||||||
|
background: var(--panel);
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
appearance: none;
|
||||||
|
background: var(--panel);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
a.button {
|
||||||
|
background: var(--black);
|
||||||
|
color: #fff;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.roadmap-tabs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin: 0 0 18px;
|
||||||
|
}
|
||||||
|
.status-pill {
|
||||||
|
border-color: var(--line);
|
||||||
|
background: var(--panel);
|
||||||
|
min-height: 56px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.status-pill::after {
|
||||||
|
content: "";
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--muted);
|
||||||
|
}
|
||||||
|
.status-pill.done::after { background: var(--green); }
|
||||||
|
.status-pill.progress::after { background: var(--blue); }
|
||||||
|
.status-pill.planned::after { background: var(--amber); }
|
||||||
|
#tasks {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
.roadmap-column {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
align-content: start;
|
||||||
|
}
|
||||||
|
.column-title {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
|
background: rgba(244, 242, 235, 0.92);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 30px;
|
border-radius: 8px;
|
||||||
padding: clamp(20px, 3vw, 32px);
|
padding: 12px 14px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.column-title span:last-child {
|
||||||
|
color: var(--muted);
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
.task {
|
||||||
|
position: relative;
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: clamp(18px, 2.4vw, 26px);
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
margin: 18px 0;
|
margin: 0;
|
||||||
|
overflow: clip;
|
||||||
|
}
|
||||||
|
.task::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 0 auto;
|
||||||
|
height: 5px;
|
||||||
|
background: var(--amber);
|
||||||
|
}
|
||||||
|
.task[data-status="done"]::before { background: var(--green); }
|
||||||
|
.task[data-status="in-progress"]::before { background: var(--blue); }
|
||||||
|
.task[data-priority="high"] {
|
||||||
|
border-color: rgba(16, 16, 15, 0.32);
|
||||||
}
|
}
|
||||||
.task-head, .subtask-top {
|
.task-head, .subtask-top {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 16px;
|
gap: 14px;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
.eyebrow {
|
.eyebrow {
|
||||||
color: var(--green);
|
color: var(--muted);
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
margin: 0 0 10px;
|
||||||
margin: 0 0 6px;
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.12em;
|
letter-spacing: 0.12em;
|
||||||
font-size: 0.78rem;
|
font-size: 0.72rem;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: clamp(1.35rem, 2vw, 2rem);
|
||||||
|
line-height: 0.98;
|
||||||
|
margin: 0;
|
||||||
|
letter-spacing: 0;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin: 24px 0 10px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
}
|
}
|
||||||
h2 { font-size: clamp(1.45rem, 3vw, 2.4rem); line-height: 1; margin: 0; letter-spacing: -0.035em; }
|
|
||||||
h3 { margin: 24px 0 10px; }
|
|
||||||
.desc, details p, .subtask p { color: var(--muted); line-height: 1.55; }
|
.desc, details p, .subtask p { color: var(--muted); line-height: 1.55; }
|
||||||
|
.desc {
|
||||||
|
font-size: 0.98rem;
|
||||||
|
margin: 18px 0 0;
|
||||||
|
}
|
||||||
details {
|
details {
|
||||||
border-top: 1px solid var(--line);
|
border-top: 1px solid var(--line);
|
||||||
border-bottom: 1px solid var(--line);
|
border-bottom: 1px solid var(--line);
|
||||||
padding: 14px 0;
|
padding: 14px 0;
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
}
|
}
|
||||||
summary { cursor: pointer; color: var(--blue); font-weight: 700; }
|
summary {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--ink);
|
||||||
|
font-weight: 850;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
}
|
||||||
.badges { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
|
.badges { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
|
||||||
.badge {
|
.badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
padding: 5px 10px;
|
padding: 6px 9px;
|
||||||
font: 700 0.78rem/1 ui-monospace, SFMono-Regular, Menlo, monospace;
|
font-size: 0.68rem;
|
||||||
background: #ece5d2;
|
font-weight: 900;
|
||||||
|
line-height: 1;
|
||||||
|
background: var(--soft);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
margin-left: 6px;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
.done { background: rgba(28, 124, 84, 0.13); color: var(--green); }
|
.done { background: rgba(22, 155, 98, 0.12); color: #09683f; }
|
||||||
.pending { background: rgba(183, 121, 31, 0.14); color: var(--amber); }
|
.pending { background: rgba(201, 134, 22, 0.14); color: #8a5709; }
|
||||||
.in-progress { background: rgba(36, 91, 132, 0.13); color: var(--blue); }
|
.in-progress { background: rgba(53, 104, 255, 0.12); color: #264fca; }
|
||||||
.blocked, .critical { background: rgba(180, 35, 24, 0.12); color: var(--red); }
|
.blocked, .critical { background: rgba(214, 76, 60, 0.14); color: #a83327; }
|
||||||
.priority-high { background: rgba(180, 35, 24, 0.12); color: var(--red); }
|
.priority-high { background: var(--black); color: #fff; }
|
||||||
.priority-medium { background: rgba(183, 121, 31, 0.14); color: var(--amber); }
|
.priority-medium { background: rgba(16, 16, 15, 0.08); color: var(--ink); }
|
||||||
.priority-low { background: rgba(102, 112, 100, 0.12); color: var(--muted); }
|
.priority-low { background: rgba(111, 113, 108, 0.12); color: var(--muted); }
|
||||||
.subtasks { list-style: none; padding: 0; margin: 0; display: grid; gap: 10px; }
|
.subtasks { list-style: none; padding: 0; margin: 0; display: grid; gap: 10px; }
|
||||||
.subtask {
|
.subtask {
|
||||||
border: 1px solid rgba(80, 67, 41, 0.16);
|
border-top: 1px solid var(--line);
|
||||||
border-radius: 18px;
|
padding: 14px 0 0;
|
||||||
padding: 14px;
|
background: transparent;
|
||||||
background: rgba(247, 239, 217, 0.48);
|
|
||||||
}
|
}
|
||||||
.sub-id {
|
.sub-id {
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
color: var(--muted);
|
||||||
color: var(--green);
|
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
min-width: 42px;
|
min-width: 42px;
|
||||||
}
|
}
|
||||||
|
.subtask strong { flex: 1; line-height: 1.2; }
|
||||||
|
.subtask p { margin: 9px 0 0 56px; font-size: 0.9rem; }
|
||||||
.deps { font-size: 0.9rem; color: var(--blue) !important; }
|
.deps { font-size: 0.9rem; color: var(--blue) !important; }
|
||||||
footer { color: var(--muted); text-align: center; padding: 30px; }
|
footer {
|
||||||
|
color: var(--muted);
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
text-align: center;
|
||||||
|
padding: 30px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
#tasks { grid-template-columns: 1fr 1fr; }
|
||||||
|
}
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
|
header { padding-top: 16px; }
|
||||||
|
.topbar { margin-bottom: 48px; align-items: flex-start; }
|
||||||
|
.nav { display: none; }
|
||||||
.hero { grid-template-columns: 1fr; }
|
.hero { grid-template-columns: 1fr; }
|
||||||
.stats { grid-template-columns: repeat(2, 1fr); }
|
.stats { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
.roadmap-tabs, #tasks { grid-template-columns: 1fr; }
|
||||||
.task-head, .subtask-top { flex-direction: column; }
|
.task-head, .subtask-top { flex-direction: column; }
|
||||||
.badges { justify-content: flex-start; }
|
.badges { justify-content: flex-start; }
|
||||||
|
.subtask p { margin-left: 0; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
|
<nav class="topbar" aria-label="Primary">
|
||||||
|
<a class="brand" href="#">
|
||||||
|
<span class="brand-mark">A</span>
|
||||||
|
Amanat
|
||||||
|
</a>
|
||||||
|
<div class="nav">
|
||||||
|
<a class="active" href="#tasks">Roadmap</a>
|
||||||
|
<a href="#search">Filter</a>
|
||||||
|
<a href="tasks.json">JSON</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Amanat docs · Taskmaster</p>
|
<p class="eyebrow">Amanat docs · Taskmaster</p>
|
||||||
<h1>Developer task queue</h1>
|
<h1>Roadmap</h1>
|
||||||
<p class="lead">A shareable static view of the docs-side Taskmaster queue generated from the PRDs and latest backend security/refactor audit. Use this page for planning; use Taskmaster CLI for status updates.</p>
|
<p class="lead">A public planning view generated from the docs-side Taskmaster queue. Track security remediation, payment architecture, refactor decisions, and completed documentation work from one place.</p>
|
||||||
</div>
|
</div>
|
||||||
<aside class="stats">
|
<aside class="stats">
|
||||||
<div class="stat"><strong>4</strong><span>parent tasks</span></div>
|
<div class="stat"><strong>4</strong><span>parent tasks</span></div>
|
||||||
@@ -186,6 +401,11 @@
|
|||||||
<select id="priority"><option value="">All priorities</option><option>high</option><option>medium</option><option>low</option></select>
|
<select id="priority"><option value="">All priorities</option><option>high</option><option>medium</option><option>low</option></select>
|
||||||
<a class="button" href="tasks.json">Raw JSON</a>
|
<a class="button" href="tasks.json">Raw JSON</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="roadmap-tabs" aria-label="Roadmap columns">
|
||||||
|
<div class="status-pill done">Shipped</div>
|
||||||
|
<div class="status-pill progress">In progress</div>
|
||||||
|
<div class="status-pill planned">Planned</div>
|
||||||
|
</div>
|
||||||
<section id="tasks">
|
<section id="tasks">
|
||||||
<article class="task" data-status="pending" data-priority="high">
|
<article class="task" data-status="pending" data-priority="high">
|
||||||
<div class="task-head">
|
<div class="task-head">
|
||||||
@@ -525,6 +745,28 @@
|
|||||||
</main>
|
</main>
|
||||||
<footer>Generated from <code>.taskmaster/tasks/tasks.json</code>. Last docs update: 2026-05-24T00:00:00.000Z</footer>
|
<footer>Generated from <code>.taskmaster/tasks/tasks.json</code>. Last docs update: 2026-05-24T00:00:00.000Z</footer>
|
||||||
<script>
|
<script>
|
||||||
|
const taskSection = document.querySelector('#tasks');
|
||||||
|
const originalCards = [...taskSection.querySelectorAll('.task')];
|
||||||
|
const columns = [
|
||||||
|
{ key: 'done', title: 'Shipped' },
|
||||||
|
{ key: 'in-progress', title: 'In progress' },
|
||||||
|
{ key: 'pending', title: 'Planned' }
|
||||||
|
];
|
||||||
|
taskSection.innerHTML = '';
|
||||||
|
const columnMap = new Map();
|
||||||
|
for (const column of columns) {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'roadmap-column';
|
||||||
|
wrapper.dataset.column = column.key;
|
||||||
|
const count = originalCards.filter((card) => card.dataset.status === column.key).length;
|
||||||
|
wrapper.innerHTML = `<div class="column-title"><span>${column.title}</span><span>${count}</span></div>`;
|
||||||
|
taskSection.appendChild(wrapper);
|
||||||
|
columnMap.set(column.key, wrapper);
|
||||||
|
}
|
||||||
|
for (const card of originalCards) {
|
||||||
|
const target = columnMap.get(card.dataset.status) || columnMap.get('pending');
|
||||||
|
target.appendChild(card);
|
||||||
|
}
|
||||||
const search = document.querySelector('#search');
|
const search = document.querySelector('#search');
|
||||||
const status = document.querySelector('#status');
|
const status = document.querySelector('#status');
|
||||||
const priority = document.querySelector('#priority');
|
const priority = document.querySelector('#priority');
|
||||||
@@ -540,6 +782,10 @@
|
|||||||
const searchMatch = !q || text.includes(q);
|
const searchMatch = !q || text.includes(q);
|
||||||
card.style.display = statusMatch && priorityMatch && searchMatch ? '' : 'none';
|
card.style.display = statusMatch && priorityMatch && searchMatch ? '' : 'none';
|
||||||
}
|
}
|
||||||
|
for (const column of document.querySelectorAll('.roadmap-column')) {
|
||||||
|
const visibleCards = [...column.querySelectorAll('.task')].filter((card) => card.style.display !== 'none').length;
|
||||||
|
column.style.display = visibleCards ? '' : 'none';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
search.addEventListener('input', applyFilters);
|
search.addEventListener('input', applyFilters);
|
||||||
status.addEventListener('change', applyFilters);
|
status.addEventListener('change', applyFilters);
|
||||||
|
|||||||
Reference in New Issue
Block a user