paste-text-hosting

byHub Nexora99

Buatkan saya sebuah website paste text / text hosting yang konsep dan alur penggunaannya mirip seperti Pastefy.com, tetapi gunakan desain dan branding sendiri. Website harus benar-benar berfungsi, bukan hanya tampilan frontend. KONSEP UTAMA Website digunakan untuk menyimpan teks secara online. Alur utama: 1. User membuka halaman utama. 2. User melihat textarea besar untuk memasukkan text. 3. User dapat paste teks apa pun ke dalam textarea. 4. User menekan tombol Create Paste / Upload. 5. Backend menyimpan teks tersebut ke database. 6. Sistem otomatis membuat ID/slug unik. 7. User mendapatkan URL unik, contohnya: https://domain.com/p/AbC123 8. Jika orang membuka URL tersebut, website menampilkan teks yang sebelumnya disimpan. 9. Setiap paste mempunyai halaman Raw. 10. Jika user membuka: https://domain.com/p/AbC123/raw maka website hanya menampilkan isi text dalam format raw/plain text tanpa UI tambahan. --- HALAMAN HOME Buat homepage modern, clean, responsive, dan mobile-friendly. Bagian utama: - Logo / nama website - Navbar - Tombol Home - Tombol Create Paste - Tombol About - Textarea besar - Placeholder: "Paste your text here..." - Tombol: "Create Paste" Tambahkan pilihan opsional: - Paste title - Expiration - Visibility - Syntax language Contoh: Title: [ My Script ] Text: [ textarea besar ] Expiration: [ Never ▼ ] Visibility: [ Public ▼ ] [ Create Paste ] --- SISTEM CREATE PASTE Ketika user menekan Create Paste: 1. Validasi bahwa textarea tidak kosong. 2. Kirim data ke backend menggunakan API. 3. Backend membuat ID unik. 4. Simpan data ke database. 5. Kembalikan URL paste. 6. Tampilkan halaman sukses. Contoh: Paste created successfully! https://domain.com/p/AbC123 Dengan tombol: [ Copy Link ] [ Open Paste ] [ Raw ] --- HALAMAN PASTE URL: /p/:id Contoh: /p/AbC123 Halaman tersebut harus menampilkan: - Judul paste - ID paste - Waktu dibuat - Bahasa/syntax jika tersedia - Isi text - Tombol Copy - Tombol Raw - Tombol Download - Tombol Back Text harus ditampilkan menggunakan font monospace. Jangan mengubah isi text. Jika text berisi: hello world 123 <script> local test() end maka tampilkan persis seperti aslinya. Gunakan escaping yang benar agar isi paste tidak dieksekusi sebagai HTML atau JavaScript. --- # RAW MODE Buat endpoint khusus: /p/:id/raw Contoh: https://domain.com/p/AbC123/raw Endpoint tersebut harus mengembalikan: Content-Type: text/plain dan body-nya hanya isi paste. Jangan tampilkan: - Navbar - HTML UI - Button - Footer - CSS - JSON wrapper Contoh jika paste berisi: print("Hello World") maka endpoint raw harus benar-benar menghasilkan: print("Hello World") bukan: { "text": "print(\"Hello World\")" } --- # DOWNLOAD Tambahkan tombol Download pada halaman paste. Ketika ditekan, browser mengunduh isi paste sebagai file text. Nama file dapat menggunakan: paste-ID.txt atau jika user memilih language tertentu: script.lua script.js script.py script.txt --- # COPY Tambahkan tombol: Copy Ketika ditekan, isi paste langsung disalin ke clipboard. Setelah berhasil: Copied! --- # DATABASE Gunakan database untuk menyimpan paste. Struktur data minimal: Paste: - id - slug - title - content - language - visibility - created_at - expires_at Gunakan ID/slug yang sulit ditebak dan unik. Contoh: /p/a8Kx29 /p/Qm92Ld /p/Zx81Pq Jangan menggunakan ID berurutan seperti: /p/1 /p/2 /p/3 --- # API Buat REST API yang jelas. Contoh: POST /api/pastes Untuk membuat paste. Request: { "title": "My Paste", "content": "Hello World", "language": "text", "visibility": "public", "expiration": null } Response: { "success": true, "id": "AbC123", "url": "/p/AbC123", "raw_url": "/p/AbC123/raw" } --- GET /api/pastes/:id Mengambil informasi paste. GET /api/pastes/:id/raw Mengambil raw content. Response endpoint raw harus berupa: Content-Type: text/plain; charset=utf-8 --- # SECURITY Pastikan website aman. Wajib: - Escape HTML pada content. - Jangan pernah mengeksekusi JavaScript yang disimpan user. - Gunakan parameterized query. - Validasi input. - Tambahkan rate limiting pada pembuatan paste. - Batasi ukuran paste. - Jangan izinkan path traversal. - Gunakan random ID. - Lindungi API dari spam. - Jangan menyimpan password atau data sensitif secara default. - Gunakan HTTPS pada production. Content paste harus dianggap sebagai DATA, bukan kode HTML yang boleh dieksekusi. --- # EXPIRATION Berikan pilihan: Never 10 Minutes 1 Hour 1 Day 7 Days 30 Days Jika expiration sudah lewat: Paste Not Found / Paste Expired Backend harus benar-benar memeriksa expiration, bukan hanya menyembunyikannya dari frontend. --- # VISIBILITY Sediakan: Public Unlisted Public: Paste dapat ditemukan jika sistem pencarian tersedia. Unlisted: Paste tetap dapat dibuka menggunakan URL langsung tetapi tidak muncul dalam daftar/search. --- # DESIGN Gunakan desain modern seperti website developer tools. Style: - Dark mode sebagai default - Background hitam/dark gray - Card modern - Border tipis - Rounded corners - Monospace font untuk text - Syntax highlighting jika language dipilih - Animasi ringan - Responsive untuk HP - Tidak terlalu banyak efek neon - Tampilan profesional - UI tidak terlalu ramai Homepage harus terlihat seperti platform developer modern. --- # PASTE VIEWER Gunakan code viewer yang nyaman untuk membaca text panjang. Fitur: - Line numbers - Horizontal scrolling - Vertical scrolling - Copy - Raw - Download - Fullscreen - Search text Jika menggunakan syntax highlighting, jangan mengubah isi asli paste. --- # RESPONSIVE MOBILE Website harus nyaman digunakan di Android. Textarea harus memenuhi lebar layar. Button jangan terlalu kecil. Navbar harus berubah menjadi mobile menu jika layar kecil. Paste viewer harus bisa horizontal scroll. --- # ERROR HANDLING Jika ID tidak ditemukan: 404 Paste Not Found Jika paste expired: Paste Expired Jika content kosong: Please enter some text. Jika server error: Something went wrong. Please try again. --- # CUSTOM DOMAIN Pastikan sistem dapat digunakan dengan domain sendiri. Contoh: https://paste.example.com Maka URL paste: https://paste.example.com/p/AbC123 Raw: https://paste.example.com/p/AbC123/raw Jangan hardcode domain localhost. Gunakan environment variable untuk base URL. --- # BACKEND Jangan membuat website sebagai frontend-only. Website harus memiliki backend yang benar-benar menyimpan data. Pilih stack yang sederhana dan mudah di-deploy. Contoh stack: Frontend: HTML + CSS + JavaScript Backend: Node.js + Express Database: SQLite untuk versi sederhana Buat struktur folder yang jelas. Contoh: project/ ├── server.js ├── package.json ├── database/ │ └── database.sqlite ├── public/ │ ├── index.html │ ├── style.css │ └── app.js └── README.md Jika menggunakan framework lain, tetap pastikan seluruh sistem dapat dijalankan dengan mudah. --- # IMPORTANT Saya ingin website yang benar-benar berfungsi. Jangan hanya membuat mockup. Jangan menggunakan localStorage sebagai database utama. Jangan membuat link palsu. Ketika user membuat paste, data harus benar-benar tersimpan di backend dan dapat diakses kembali melalui URL. Pastikan: /p/:id dan /p/:id/raw benar-benar bekerja. Berikan seluruh source code lengkap. Berikan: 1. Struktur folder 2. package.json 3. Backend 4. Database setup 5. Frontend 6. CSS 7. JavaScript 8. API 9. Sistem raw 10. Sistem download 11. Sistem copy 12. Sistem expiration 13. Sistem visibility 14. Security dasar 15. Cara menjalankan di localhost 16. Cara deploy ke hosting 17. Cara menghubungkan custom domain Jangan memberikan potongan kode. Berikan kode lengkap yang siap di-copy dan dijalankan.

No preview

Comments (0)

No comments yet. Be the first!

System Requirements

Page 1 of 26

System Requirements Document for paste-text-hosting

1. Introduction

paste-text-hosting is a self-hosted, working text-hosting (paste bin) web application. It lets anyone paste text into a large composer on the homepage, submit it to a real backend, and receive a unique, hard-to-guess URL such as https://domain.com/p/AbC123. Anyone who opens that URL sees the stored text exactly as it was written, and every paste also exposes a raw endpoint at /p/:id/raw that returns only the paste body as text/plain.

The product intent is a functioning developer utility, not a mockup: content is genuinely persisted in a database, retrievable by URL, and served through a clear REST API. The concept and user flow are conceptually similar to Pastefy.com, but the design and branding are original — a dark, information-first developer platform built on a visible grid, ruled metadata rows, and wayfinding colour codes rather than a neon or lifestyle-app aesthetic.

The audience is developers, sysadmins, students, and self-hosters who paste code, logs, and configuration all day and need a calm, rigorous, trustworthy place to store and share text. The application is anonymous by design: no accounts, no login, and no passwords or sensitive data stored by default.

Page 2 of 26

2. System Overview

paste-text-hosting is delivered as a server-rendered/static frontend plus a real backend and a database. The frontend is HTML, CSS, and JavaScript; the backend is Node.js with Express; the database is SQLite for the simple version. The backend owns paste creation, unique slug generation, persistence, expiration enforcement, visibility handling, and the raw plain-text response. The frontend owns the composer, the success state, and the paste reader with its copy, raw, download, fullscreen, and search controls.

Actors. Two accepted human personas use the product: the Paste Creator, who submits non-empty text with optional metadata and receives a unique URL, and the Paste Reader, who opens a paste URL to read or retrieve the exact original content. There are no other accepted human actors. The backend, the database, and the browser clipboard are non-persona system actors.

Accepted behaviour. Creating a paste (with title, expiration, visibility, and syntax language options), generating a unique non-sequential slug, storing the record, returning the paste URL and raw URL, showing a success state with Copy Link / Open Paste / Raw, rendering the paste page with metadata and unmodified monospace content, serving the raw endpoint as plain text only, downloading the content as a text file, copying to clipboard with a "Copied!" confirmation, enforcing expiration on the backend, distinguishing Public from Unlisted visibility, and applying the stated security controls.

Ownership. All accepted human-facing behaviour is owned by first-party application pages: Home, Create Paste, Paste Created, /p/:id, /p/:id/raw, and About. The REST endpoints /api/pastes, /api/pastes/:id, and /api/pastes/:id/raw are first-party application surfaces that serve the frontend and any external API consumer. No provider-owned or external-destination surfaces are accepted.

Narrow exclusions. This document does not add accounts, login, OAuth, folders, tags, rich previews (Markdown/Mermaid/CSV/GeoJSON/Diff/Calendar/Regex/Asciinema), embeds, QR codes, code screenshots, OS-level social sharing, SDKs, CLI clients, VS Code or Raycast extensions, utility/convert tools, client-side encryption, or an Explore/search listing page. These appear in the inspiration-only reference and are not accepted product scope. The visibility rule's phrase "if a search system exists" is preserved as a conditional and does not itself create a search system.

Page 3 of 26

2a. Product Interpretation and Delivery Boundary

The product is a first-party, self-hostable web application with a real backend. The user explicitly requires that the site is not frontend-only, that data is genuinely stored in a backend database, that localStorage is not used as the primary database, and that no fake links or mockups are produced. /p/:id and /p/:id/raw must genuinely work.

Access ownership. Every accepted destination is anonymously reachable. The source never asks for accounts, login, or user identity, and it explicitly says not to store passwords or sensitive data by default. There is therefore no application-owned identity, no first-use identity establishment, and no protected destination in the current scope. Paste URLs are the access mechanism: anyone holding a URL can read the paste, and Unlisted pastes are reachable only by direct URL.

Delivery boundary. Current scope is the working paste host described above, runnable on localhost and deployable to hosting with a custom domain. The base URL must come from an environment variable rather than a hardcoded localhost, so the same build serves https://paste.example.com/p/AbC123 and https://paste.example.com/p/AbC123/raw. HTTPS is required in production. Nothing in the current scope is deferred to a future horizon; the future section records only the inspiration-only reference capabilities that were not accepted.

2b. Source Content Inventory

Not applicable. The single reference directive (Pastefy.com) declares uses: ["feature_reference", "structure_reference"] with authority: "inspiration_only" and does not declare content_source. No factual content-source inventory is therefore included, and no Pastefy.com names, copy, features, or facts are carried into product scope.

Page 4 of 26

2c. Page Content and Component Coverage

Home

  • Information/state: Branded wordmark/logo and site name; navbar with Home, Create Paste, and About; an oversized flush-left headline block; a full-width red rule that doubles as the composer's top edge; a stacked metadata column of ruled label/value rows (ENDPOINT /api/pastes, RAW /p/:id/raw, EXPIRY NEVER → 30D, VISIBILITY PUBLIC / UNLISTED) with a yellow "LIVE" chip on the first row.
  • Primary action: The composer — a full-width title input on its own row, a 60vh monospace textarea with placeholder Paste your text here..., a three-cell metadata row (Expiration / Visibility / Syntax) rendered as label-over-value pairs with hard vertical rules, and a red Create Paste button pinned flush right beneath.
  • Supporting actions: Navbar navigation to Home, Create Paste, and About; mobile menu toggle on small screens.
  • Domain entities: Paste (title, content, language, visibility, expiration) as an in-progress draft; Expiration options (Never, 10 Minutes, 1 Hour, 1 Day, 7 Days, 30 Days); Visibility options (Public, Unlisted); Syntax language selection.
  • Component responsibilities: Navbar (desktop links, mobile menu); hero headline block; composer form (title input, textarea, metadata cells, submit button); inline validation message region; metadata wayfinding column.
  • States:
    • Loading: submit button enters a pending state while the create request is in flight.
    • Empty: textarea shows the placeholder Paste your text here...; Expiration defaults to Never; Visibility defaults to Public.
    • Success: the red rule sweeps once across the top rule and the view transitions to the Paste Created state.
    • Error: empty content shows Please enter some text.; server failure shows Something went wrong. Please try again.; the draft content is preserved so the creator can retry.
Page 5 of 26

Create Paste

  • Information/state: The same composer surface reached as its own navigation destination, with title, textarea, Expiration, Visibility, and Syntax fields and their current values.
  • Primary action: Submit the paste to the backend via the API.
  • Supporting actions: Clear/reset the draft; navigate back to Home or to About.
  • Domain entities: Paste draft; Expiration; Visibility; Syntax language.
  • Component responsibilities: Composer form; validation region; submit control; metadata cells.
  • States:
    • Loading: submit disabled with a pending indicator; no duplicate submission.
    • Empty: placeholder text and default metadata values.
    • Success: transition to Paste Created with the returned URL and raw URL.
    • Error: Please enter some text. for empty content; Something went wrong. Please try again. for server errors; draft retained for recovery.
Page 6 of 26

Paste Created

  • Information/state: The confirmation headline Paste created successfully!, the full paste URL, and the raw URL, presented as ruled label/value rows.
  • Primary action: Copy Link — copies the paste URL to the clipboard and confirms.
  • Supporting actions: Open Paste — navigates to /p/:id; Raw — navigates to /p/:id/raw.
  • Domain entities: The created Paste record (id, slug, url, raw_url).
  • Component responsibilities: Success headline; URL display rows; Copy Link, Open Paste, and Raw controls; the one-time red rule sweep as the success transition.
  • States:
    • Loading: not applicable beyond the transition from submission.
    • Empty: not reachable without a successfully created paste.
    • Success: URL and raw URL displayed with working Copy Link, Open Paste, and Raw actions.
    • Error: if the clipboard write is unavailable, the URL remains fully visible and selectable so the creator can copy it manually.
Page 7 of 26

/p/:id

  • Information/state: Paste title; paste ID; creation time; language/syntax when available; visibility; expiration; and the stored text content. Metadata is rendered as transit-style ruled rows — 11px uppercase label over a tabular value, separated by hairlines.
  • Primary action: Read the paste content in the code viewer.
  • Supporting actions: Copy (with Copied! confirmation), Raw, Download, Back, Fullscreen, and Search text.
  • Domain entities: Paste (id, slug, title, content, language, visibility, created_at, expires_at).
  • Component responsibilities: Metadata header of ruled label/value rows; code viewer with a permanently visible yellow line-number gutter on its own darker column, a vertical hairline separating gutter from content, and horizontal scroll that moves content only while the gutter stays put; ruled action bar including the persistent /raw toggle that swaps the view in place and shows the literal text/plain; charset=utf-8 header string as a ruled caption; search control with match highlighting in the wayfinding colour code.
  • States:
    • Loading: the code viewer fades in over 1.2s on load.
    • Empty: not applicable — empty content is rejected at creation.
    • Success: content displayed in monospace, unmodified, with correct escaping so stored content is never executed as HTML or JavaScript; line numbers, horizontal and vertical scrolling, copy, raw, download, fullscreen, and search all available.
    • Error: unknown ID shows 404 Paste Not Found; expired paste shows Paste Expired; server failure shows Something went wrong. Please try again.
    • Recovery: from an error state the reader can use Back to return to the previous context or navigate Home.
Page 8 of 26

/p/:id/raw

  • Information/state: The paste body only. No navbar, no HTML UI, no buttons, no footer, no CSS, and no JSON wrapper.
  • Primary action: None — this is a data line, consumed directly by browsers, curl, and scripts.
  • Supporting actions: None.
  • Domain entities: Paste content.
  • Component responsibilities: Response headers set Content-Type: text/plain; charset=utf-8; the body is the exact stored content.
  • States:
    • Loading: not applicable.
    • Empty: not applicable — empty content is rejected at creation.
    • Success: the raw body is byte-faithful to the stored content, e.g. a paste containing print("Hello World") returns exactly print("Hello World") and never {"text": "print(\"Hello World\")"}.
    • Error: unknown ID returns 404 Paste Not Found; expired paste returns Paste Expired; server failure returns Something went wrong. Please try again.

About

  • Information/state: Product information for the branded text host, including the diagrammatic route map showing POST /api/pastes → slug → /p/:id → /p/:id/raw as connected nodes on a grid.
  • Primary action: Read the product explanation and follow the route map.
  • Supporting actions: Navigate to Home or Create Paste.
  • Domain entities: The documented endpoints and the slug/route relationship.
  • Component responsibilities: Ruled content sections; SVG system diagram; pictogram icons at 1.5px stroke.
  • States:
    • Loading: static content, no loading state required.
    • Empty: not applicable.
    • Success: content and diagram render within the grid at all viewports.
    • Error: not applicable.
Page 9 of 26

/api/pastes

  • Information/state: The paste creation REST endpoint. Accepts title, content, language, visibility, and expiration; returns success, id, url, and raw_url.
  • Primary action: POST — create a paste.
  • Supporting actions: None.
  • Domain entities: Paste record creation; unique slug generation.
  • Component responsibilities: Input validation; size limit enforcement; rate limiting; parameterized insert; slug generation; response serialization.
  • States:
    • Loading: not applicable.
    • Empty: empty or missing content is rejected with the empty-content error.
    • Success: {"success": true, "id": "AbC123", "url": "/p/AbC123", "raw_url": "/p/AbC123/raw"}.
    • Error: validation failure, size-limit rejection, rate-limit rejection, and server failure each return an appropriate error response; Something went wrong. Please try again. is the user-facing server-error message.
Page 10 of 26

/api/pastes/:id

  • Information/state: The paste information retrieval endpoint.
  • Primary action: GET — retrieve paste information.
  • Supporting actions: None.
  • Domain entities: Paste (id, slug, title, content, language, visibility, created_at, expires_at).
  • Component responsibilities: Parameterized lookup; expiration check; not-found handling.
  • States:
    • Loading: not applicable.
    • Empty: not applicable.
    • Success: paste information returned for a live paste.
    • Error: unknown ID returns 404 Paste Not Found; expired paste returns Paste Expired; server failure returns Something went wrong. Please try again.
Page 11 of 26

/api/pastes/:id/raw

  • Information/state: The raw content retrieval endpoint.
  • Primary action: GET — retrieve raw content.
  • Supporting actions: None.
  • Domain entities: Paste content.
  • Component responsibilities: Parameterized lookup; expiration check; plain-text serialization with no wrapper.
  • States:
    • Loading: not applicable.
    • Empty: not applicable.
    • Success: Content-Type: text/plain; charset=utf-8 with the paste body only.
    • Error: unknown ID returns 404 Paste Not Found; expired paste returns Paste Expired; server failure returns Something went wrong. Please try again.
Page 12 of 26

3. Functional Requirements

FR-1 — Working text-hosting website with original design and branding (explicit) As a Paste Creator, I should use a working text-hosting website whose concept and user flow are conceptually similar to Pastefy.com but whose design and branding are original, so that I have a functional, distinct paste host rather than a clone.

  • Lifecycle: Initiator: Paste Creator. Trigger: opening the site. Observable result: a branded, functioning paste host. Failure/recovery: server errors surface Something went wrong. Please try again. Continuation: the creator proceeds to compose a paste.
  • Acceptance: The site is genuinely functional, not a frontend-only mockup; branding and design are original.

FR-2 — Homepage structure (explicit) As a Paste Creator, I should see a modern, clean, responsive, mobile-friendly homepage containing a logo/site name, a navbar with Home, Create Paste, and About buttons, a large textarea with the placeholder Paste your text here..., and a Create Paste button, so that I can immediately start a paste.

  • Lifecycle: Initiator: Paste Creator. Trigger: opening the homepage. Observable result: the composer and navigation are visible and usable. Failure/recovery: layout remains usable at 375px, 768px, and 1280px. Continuation: the creator types or pastes text.
  • Acceptance: All listed elements are present; the textarea is large and fills the screen width on mobile.

FR-3 — Optional paste metadata fields (explicit) As a Paste Creator, I should be able to optionally set a paste title, expiration, visibility, and syntax language, laid out as Title, Text textarea, Expiration (default Never), Visibility (default Public), and Create Paste, so that I can describe and control the paste before submitting.

  • Lifecycle: Initiator: Paste Creator. Trigger: interacting with the composer fields. Observable result: the chosen metadata is attached to the submission. Failure/recovery: invalid selections fall back to the documented defaults. Continuation: the creator submits.
  • Acceptance: Title input, Expiration select defaulting to Never, Visibility select defaulting to Public, and Syntax language selection are all present and optional.

FR-4 — Create Paste flow (explicit) As a Paste Creator, I should have my submission validated, sent to the backend via the API, assigned a unique ID, stored in the database, and answered with the paste URL and a success page, so that my text is genuinely saved and reachable.

  • Lifecycle: Initiator: Paste Creator. Trigger: pressing Create Paste. Observable result: a stored paste and a success page. Failure/recovery: empty content shows Please enter some text.; server failure shows Something went wrong. Please try again. with the draft preserved. Continuation: the creator copies the link, opens the paste, or opens the raw view.
  • Acceptance: Validation rejects empty textarea; data is sent via API; the backend generates a unique ID; the record is stored in the database; the URL is returned; the success page is shown.

FR-5 — Success page content and actions (explicit) As a Paste Creator, I should see Paste created successfully! with the paste URL and the buttons Copy Link, Open Paste, and Raw, so that I can immediately share or inspect my paste.

  • Lifecycle: Initiator: Paste Creator. Trigger: successful creation. Observable result: the confirmation headline, the URL, and three working actions. Failure/recovery: if the clipboard is unavailable, the URL stays visible and selectable. Continuation: Open Paste goes to /p/:id; Raw goes to /p/:id/raw.
  • Acceptance: The exact headline Paste created successfully! is shown with the URL and all three buttons.

FR-6 — Paste page content (explicit) As a Paste Reader, I should see the paste title, paste ID, creation time, language/syntax if available, the text content, and Copy, Raw, Download, and Back buttons at /p/:id, so that I can read the paste and act on it.

  • Lifecycle: Initiator: Paste Reader. Trigger: opening /p/AbC123. Observable result: metadata and content rendered. Failure/recovery: unknown ID shows 404 Paste Not Found; expired paste shows Paste Expired. Continuation: the reader copies, downloads, opens raw, or goes back.
  • Acceptance: All listed metadata and controls are present at the /p/:id route.

FR-7 — Unmodified monospace content with correct escaping (explicit) As a Paste Reader, I should see the paste content in a monospace font, unmodified, with correct escaping so stored content is never executed as HTML or JavaScript, so that I read exactly what was written.

  • Lifecycle: Initiator: Paste Reader. Trigger: viewing a paste. Observable result: byte-faithful display. Failure/recovery: escaping failures are treated as defects, not acceptable states. Continuation: the reader continues reading or copies the content.
  • Acceptance: A paste containing hello\nworld\n123\n\n<script>\nlocal test()\nend displays exactly as written, and the <script> block is never executed.

FR-8 — Raw mode endpoint (explicit) As a Paste Reader, I should get Content-Type: text/plain with only the paste body from /p/:id/raw, with no navbar, HTML UI, buttons, footer, CSS, or JSON wrapper, so that I can consume the paste as clean data.

  • Lifecycle: Initiator: Paste Reader. Trigger: opening /p/AbC123/raw. Observable result: plain-text body only. Failure/recovery: unknown ID returns 404 Paste Not Found; expired paste returns Paste Expired. Continuation: the reader pipes the output into other tools.
  • Acceptance: A paste containing print("Hello World") returns exactly print("Hello World"), never {"text": "print(\"Hello World\")"}.

FR-9 — Download (explicit) As a Paste Reader, I should download the paste content as a text file from the paste page, named paste-ID.txt or, when a language is chosen, script.lua, script.js, script.py, or script.txt, so that I can keep the content locally.

  • Lifecycle: Initiator: Paste Reader. Trigger: pressing Download. Observable result: the browser downloads the file with the correct name and content. Failure/recovery: if the download cannot start, the reader can still use Copy or Raw. Continuation: the reader continues reading or leaves the page.
  • Acceptance: The downloaded file contains the exact paste content and uses the documented naming.

FR-10 — Copy (explicit) As a Paste Reader, I should have the paste content copied to my clipboard when I press Copy, with a Copied! confirmation on success, so that I can reuse the text elsewhere.

  • Lifecycle: Initiator: Paste Reader. Trigger: pressing Copy. Observable result: clipboard contains the exact content and Copied! is shown. Failure/recovery: if the clipboard write fails, the content remains selectable in the viewer. Continuation: the reader pastes the content elsewhere.
  • Acceptance: The clipboard receives the exact content and Copied! appears on success.

FR-11 — Database record structure (explicit) As the system, I should store each paste with at least id, slug, title, content, language, visibility, created_at, and expires_at, so that every paste is fully persisted and retrievable.

  • Lifecycle: Initiator: Paste Creator (via submission). Trigger: a successful create request. Observable result: a durable database row. Failure/recovery: a failed insert returns a server error and no partial record. Continuation: the record is retrievable by slug.
  • Acceptance: All eight fields exist and are populated appropriately.

FR-12 — Unique, hard-to-guess slugs (explicit) As the system, I should generate unique, hard-to-guess IDs/slugs such as /p/a8Kx29, /p/Qm92Ld, and /p/Zx81Pq, and I should never use sequential IDs like /p/1, /p/2, /p/3, so that pastes cannot be enumerated.

  • Lifecycle: Initiator: system, on creation. Trigger: a create request. Observable result: a random unique slug. Failure/recovery: a collision triggers regeneration. Continuation: the slug is returned in the response.
  • Acceptance: Slugs are random and unique; sequential numeric slugs are never produced.

FR-13 — POST /api/pastes (explicit) As an API consumer, I should create a paste by POSTing title, content, language, visibility, and expiration to /api/pastes and receive success, id, url, and raw_url, so that I can create pastes programmatically.

  • Lifecycle: Initiator: API consumer (acting for the Paste Creator). Trigger: an HTTP POST. Observable result: the documented JSON response. Failure/recovery: validation, size-limit, rate-limit, and server errors return appropriate error responses. Continuation: the consumer uses the returned URLs.
  • Acceptance: Request {"title": "My Paste", "content": "Hello World", "language": "text", "visibility": "public", "expiration": null} returns {"success": true, "id": "AbC123", "url": "/p/AbC123", "raw_url": "/p/AbC123/raw"}.

FR-14 — GET /api/pastes/:id (explicit) As an API consumer, I should retrieve paste information from /api/pastes/:id, so that I can inspect a paste's metadata and content programmatically.

  • Lifecycle: Initiator: API consumer. Trigger: an HTTP GET. Observable result: paste information. Failure/recovery: unknown ID returns 404 Paste Not Found; expired paste returns Paste Expired. Continuation: the consumer uses the data.
  • Acceptance: The endpoint returns paste information for a live paste and the documented errors otherwise.

FR-15 — GET /api/pastes/:id/raw (explicit) As an API consumer, I should retrieve raw content from /api/pastes/:id/raw with Content-Type: text/plain; charset=utf-8, so that I can consume the paste body without a wrapper.

  • Lifecycle: Initiator: API consumer. Trigger: an HTTP GET. Observable result: the plain-text body. Failure/recovery: unknown ID returns 404 Paste Not Found; expired paste returns Paste Expired. Continuation: the consumer processes the text.
  • Acceptance: The response header is Content-Type: text/plain; charset=utf-8 and the body is the paste content only.

FR-16 — Security controls (explicit) As the system, I should escape HTML in content, never execute stored JavaScript, use parameterized queries, validate input, rate limit paste creation, limit paste size, prevent path traversal, use random IDs, protect the API from spam, avoid storing passwords or sensitive data by default, and use HTTPS in production, treating paste content as data rather than executable HTML, so that the service is safe to run publicly.

  • Lifecycle: Initiator: system, on every request. Trigger: any create or read request. Observable result: content is stored and served as inert data; abusive creation is throttled. Failure/recovery: rejected requests return clear errors without leaking internals. Continuation: legitimate use continues unaffected.
  • Acceptance: Each listed control is implemented and verifiable; paste content is never executed as HTML or JavaScript.

FR-17 — Expiration options and enforcement (explicit) As a Paste Creator, I should choose Never, 10 Minutes, 1 Hour, 1 Day, 7 Days, or 30 Days, and expired pastes should show Paste Not Found / Paste Expired, with the backend genuinely checking expiration rather than only hiding it in the frontend, so that time-limited pastes truly expire.

  • Lifecycle: Initiator: Paste Creator (selection); system (enforcement). Trigger: retrieval after the expiry moment. Observable result: the paste is unavailable. Failure/recovery: the reader sees Paste Expired and can navigate away. Continuation: the reader returns Home or opens another paste.
  • Acceptance: All six options are offered; the backend enforces expiry on both the paste page and the raw endpoint.

FR-18 — Visibility options (explicit) As a Paste Creator, I should choose Public or Unlisted, where Public pastes can be found if a search system exists and Unlisted pastes remain openable via direct URL but do not appear in listings/search, so that I can control discoverability.

  • Lifecycle: Initiator: Paste Creator. Trigger: selecting visibility at creation. Observable result: the visibility is stored and honored. Failure/recovery: an invalid value falls back to the documented default. Continuation: the creator shares the direct URL.
  • Acceptance: Both options exist; Unlisted pastes are reachable by direct URL and excluded from any listing or search surface.

FR-19 — Developer-tools design (explicit) As a Paste Creator and Paste Reader, I should use a modern developer-tools interface with dark mode by default, black/dark gray background, modern cards, thin borders, rounded corners, monospace font for text, syntax highlighting when a language is selected, light animation, mobile responsiveness, restrained neon, a professional look, and an uncluttered UI, so that the homepage reads as a modern developer platform.

  • Lifecycle: Initiator: both personas. Trigger: any page view. Observable result: a consistent dark, professional interface. Failure/recovery: layout and contrast remain usable at all supported viewports. Continuation: normal use.
  • Acceptance: All listed style attributes are present; the homepage looks like a modern developer platform.

FR-20 — Paste viewer (explicit) As a Paste Reader, I should read long text comfortably with line numbers, horizontal scrolling, vertical scrolling, Copy, Raw, Download, Fullscreen, and text search, and syntax highlighting must not alter the original paste content, so that long pastes stay readable and faithful.

  • Lifecycle: Initiator: Paste Reader. Trigger: opening a paste. Observable result: a readable viewer with all listed controls. Failure/recovery: if highlighting is unavailable, the plain monospace content still renders unmodified. Continuation: the reader searches, copies, downloads, or goes fullscreen.
  • Acceptance: All eight viewer capabilities are present and the displayed content is identical to the stored content.

FR-21 — Responsive mobile behaviour (explicit) As a Paste Reader on Android, I should have a comfortable experience where the textarea fills the screen width, buttons are not too small, the navbar becomes a mobile menu on small screens, and the paste viewer supports horizontal scroll, so that the site is usable on a phone.

  • Lifecycle: Initiator: both personas on mobile. Trigger: viewing at a small viewport. Observable result: a usable mobile layout. Failure/recovery: content wraps or scrolls rather than overflowing. Continuation: normal use.
  • Acceptance: All four mobile behaviours are present at small viewports.

FR-22 — Error handling messages (explicit) As a Paste Reader, I should see 404 Paste Not Found for an unknown ID, Paste Expired for an expired paste, Please enter some text. for empty content, and Something went wrong. Please try again. for server errors, so that I always understand what happened.

  • Lifecycle: Initiator: system. Trigger: an error condition. Observable result: the exact documented message. Failure/recovery: the reader can retry or navigate away. Continuation: the reader returns to a working state.
  • Acceptance: Each condition produces its exact documented message.

FR-23 — Custom domain support (explicit) As an operator, I should run the system on my own domain such as https://paste.example.com, producing https://paste.example.com/p/AbC123 and https://paste.example.com/p/AbC123/raw, with no hardcoded localhost and an environment variable for the base URL, so that the deployment is portable.

  • Lifecycle: Initiator: operator (deployment). Trigger: configuring the environment. Observable result: correct absolute URLs on the configured domain. Failure/recovery: a missing base URL falls back to a documented default rather than a hardcoded localhost. Continuation: the operator serves traffic on the custom domain.
  • Acceptance: No hardcoded localhost; the base URL comes from an environment variable.

FR-24 — Real backend and simple deployable stack (explicit) As an operator, I should run a site that is not frontend-only, with a real backend that stores data, using a simple, easy-to-deploy stack such as HTML + CSS + JavaScript frontend, Node.js + Express backend, and SQLite for the simple version, with a clear folder structure including server.js, package.json, database/database.sqlite, public/index.html, public/style.css, public/app.js, and README.md, so that I can run and deploy it easily.

  • Lifecycle: Initiator: operator. Trigger: installation and startup. Observable result: a running server with a persistent database. Failure/recovery: startup errors are reported clearly in the README and logs. Continuation: the operator deploys to hosting.
  • Acceptance: The backend genuinely stores data; the folder structure is clear and matches the documented layout.

FR-25 — Complete ready-to-run deliverables (explicit) As an operator, I should receive complete, ready-to-copy-and-run source code covering folder structure, package.json, backend, database setup, frontend, CSS, JavaScript, API, raw system, download system, copy system, expiration system, visibility system, basic security, how to run on localhost, how to deploy to hosting, and how to connect a custom domain, with no partial snippets, so that I can run the whole system immediately.

  • Lifecycle: Initiator: operator. Trigger: receiving the deliverables. Observable result: a complete runnable project. Failure/recovery: any missing piece is a defect. Continuation: the operator runs and deploys.
  • Acceptance: All seventeen listed deliverable areas are covered with complete code, not snippets.

FR-26 — Genuine functionality, no mockups (explicit) As a Paste Creator, I should have my paste genuinely stored in the backend and retrievable via URL, with /p/:id and /p/:id/raw really working, and with no mockups, no localStorage as the primary database, and no fake links, so that the product is trustworthy.

  • Lifecycle: Initiator: Paste Creator. Trigger: creating and then opening a paste. Observable result: the same content returned from the backend. Failure/recovery: any failure to retrieve a stored paste is a defect. Continuation: the creator shares the working URL.
  • Acceptance: Created pastes are stored server-side and retrievable at both routes; localStorage is not the primary database.

FR-27 — Backend database persistence for paste records (required_inference) As the system, I should persist each accepted paste as a durable database record so that it survives process restarts and remains retrievable by slug.

  • Lifecycle: Initiator: system. Trigger: a successful create request. Observable result: a committed row. Failure/recovery: a failed commit returns a server error and no partial record. Continuation: retrieval by slug succeeds.
  • Acceptance: A paste created in one session is retrievable after a server restart.

FR-28 — Unique random slug generation (required_inference) As the system, I should generate a random slug and verify its uniqueness before committing the record, so that every paste URL resolves to exactly one paste.

  • Lifecycle: Initiator: system. Trigger: a create request. Observable result: a unique random slug. Failure/recovery: a collision triggers regeneration. Continuation: the slug is returned in the response.
  • Acceptance: No two pastes share a slug; slugs are not sequential.

FR-29 — Backend expiration validation during retrieval (required_inference) As the system, I should evaluate expires_at on every retrieval path — the paste page, the raw route, and both API endpoints — so that expiry is enforced server-side.

  • Lifecycle: Initiator: system. Trigger: any retrieval of a paste with a past expires_at. Observable result: the paste is treated as unavailable. Failure/recovery: the reader sees Paste Expired. Continuation: the reader returns to a working state.
  • Acceptance: An expired paste is unavailable through every retrieval path, not merely hidden in the frontend.

FR-30 — Plain-text raw response handling (required_inference) As the system, I should serialize the raw response as plain text with Content-Type: text/plain; charset=utf-8 and no wrapper, so that consumers receive the paste body exactly.

  • Lifecycle: Initiator: system. Trigger: a raw request. Observable result: a plain-text body. Failure/recovery: errors return the documented messages. Continuation: the consumer processes the text.
  • Acceptance: The raw body equals the stored content with no JSON or HTML wrapper.

FR-31 — Configured environment-based deployment URL (required_inference) As the system, I should build absolute paste and raw URLs from a configured base URL environment variable, so that the same build serves any domain.

  • Lifecycle: Initiator: system. Trigger: generating a response URL. Observable result: URLs on the configured domain. Failure/recovery: a missing value falls back to a documented default. Continuation: the operator changes the domain without code changes.
  • Acceptance: No hardcoded localhost appears in generated URLs.

FR-32 — Input validation, size limits, parameterized queries, rate limiting, and escaping (required_inference) As the system, I should validate all input, enforce a paste size limit, use parameterized queries for every database access, rate limit paste creation, and escape content on output, so that the security requirements are actually enforced.

  • Lifecycle: Initiator: system. Trigger: any create or read request. Observable result: only valid, bounded, safely stored and safely rendered data. Failure/recovery: rejected requests return clear errors. Continuation: legitimate use continues.
  • Acceptance: Each control is implemented and verifiable; no string-concatenated SQL exists; oversized or malformed input is rejected.
Page 13 of 26

4. User Personas

Paste Creator

The Paste Creator is the persona who initiates the product's core transaction. They arrive at the homepage with text in hand — a script, a log excerpt, a configuration file, a snippet they want to hand to someone else — and their recurring responsibility is to submit non-empty content to the backend so that it is genuinely stored and a unique slug/URL is returned.

Their work is defined by the composer: they paste or type into the large textarea, optionally set a title, choose an expiration from Never, 10 Minutes, 1 Hour, 1 Day, 7 Days, or 30 Days, choose Public or Unlisted visibility, and select a syntax language. They then press Create Paste. Their success is the confirmation state showing Paste created successfully! with the paste URL and the Copy Link, Open Paste, and Raw actions.

What makes this role distinct from the reader is that the creator owns the commitment: they decide what is stored, for how long, and how discoverable it is. They are the only persona who experiences the validation failure Please enter some text., the only one who sees the success transition, and the only one who needs the returned URL to share. They interact with the Paste Reader indirectly — the URL they copy is the reader's entry point — and with the system through the create API. Their observable success is a working URL that returns their exact text.

Page 14 of 26

Paste Reader

The Paste Reader is anyone who opens a paste URL such as /p/AbC123. They may be the creator returning to their own paste, a colleague who received the link, or a stranger following a shared URL. Their responsibility is to read or retrieve the exact original content.

Their work is defined by the paste page and the raw endpoint. On /p/:id they rely on the metadata header — title, paste ID, creation time, language/syntax when available, visibility, and expiration — and on the code viewer with its line numbers, horizontal and vertical scrolling, search, fullscreen, Copy, Raw, Download, and Back controls. On /p/:id/raw they rely on a plain-text response with no UI at all, suitable for curl and scripts.

What makes this role distinct from the creator is that they hold no authoring responsibility: they cannot change what is stored, and their only decisions are how to consume it — copy it, download it, open the raw line, search within it, or go back. They are the persona who experiences 404 Paste Not Found and Paste Expired, and their observable success is reading or retrieving the exact original content, or receiving a clear, honest error when the paste is unavailable.

5. Core User Flows

Page 15 of 26

Flow 1 — Paste Creator creates a paste from the homepage

  1. The Paste Creator opens the homepage. The branded wordmark, navbar (Home, Create Paste, About), oversized headline, red rule, and metadata wayfinding column render on the ink ground.
  2. The creator sees the composer: a full-width title input, a 60vh monospace textarea showing the placeholder Paste your text here..., and a three-cell metadata row for Expiration, Visibility, and Syntax.
  3. The creator pastes their text into the textarea. The content is held as a draft; nothing is stored yet.
  4. Optionally, the creator types a title such as My Script, selects an expiration (default Never), selects a visibility (default Public), and selects a syntax language.
  5. The creator presses Create Paste. The frontend validates that the textarea is not empty.
  6. If the textarea is empty, the composer shows Please enter some text. and the creator returns to step 3.
  7. The frontend sends the data to the backend via POST /api/pastes with title, content, language, visibility, and expiration.
  8. The backend validates the input, enforces the paste size limit and rate limit, generates a unique random slug, and stores the record in the database with id, slug, title, content, language, visibility, created_at, and expires_at.
  9. The backend returns {"success": true, "id": "AbC123", "url": "/p/AbC123", "raw_url": "/p/AbC123/raw"}.
  10. The red rule sweeps once across the top rule as the success transition, and the Paste Created state appears showing Paste created successfully! with the paste URL and the Copy Link, Open Paste, and Raw buttons.
  11. If the server fails, the composer shows Something went wrong. Please try again. and the draft is preserved so the creator can retry from step 5.
  12. Continuation: the creator presses Copy Link to copy the URL (confirmed on success), Open Paste to go to /p/:id, or Raw to go to /p/:id/raw.

Flow 2 — Paste Creator creates a paste from the Create Paste destination

  1. The Paste Creator opens the Create Paste destination from the navbar.
  2. The same composer surface renders with title, textarea, Expiration, Visibility, and Syntax fields.
  3. The creator enters content and optional metadata, then presses Create Paste.
  4. The same validation, submission, slug generation, persistence, and response steps as Flow 1 occur.
  5. On success the creator lands on the Paste Created state with the URL and the Copy Link, Open Paste, and Raw actions.
  6. On empty content the creator sees Please enter some text.; on server failure they see Something went wrong. Please try again. with the draft preserved.
  7. Continuation: the creator copies the link or opens the paste.
Page 16 of 26

Flow 3 — Paste Reader reads a paste

  1. The Paste Reader opens a paste URL such as /p/AbC123.
  2. The backend looks up the slug with a parameterized query and checks expires_at.
  3. If the slug does not exist, the reader sees 404 Paste Not Found and can use Back or navigate Home.
  4. If the paste has expired, the reader sees Paste Expired and can use Back or navigate Home.
  5. If the paste is live, the paste page renders the metadata header as ruled label/value rows — title, paste ID, creation time, language/syntax if available, visibility, and expiration — followed by the code viewer.
  6. The code viewer fades in over 1.2s and displays the content in monospace, unmodified, with a permanently visible yellow line-number gutter on its own darker column and a vertical hairline separating gutter from content.
  7. The reader scrolls vertically through long content and horizontally when lines are wide; the gutter stays put while content moves.
  8. The reader may use Search text to find a string, with matches highlighted in the wayfinding colour code.
  9. The reader may press Fullscreen to expand the viewer for long reading.
  10. Continuation: the reader presses Copy (clipboard receives the exact content and Copied! appears), Download (the browser downloads paste-ID.txt or script.lua / script.js / script.py / script.txt when a language was chosen), Raw (the view swaps in place and shows the literal text/plain; charset=utf-8 header string as a ruled caption), or Back to return to the previous context.

Flow 4 — Paste Reader retrieves the raw content

  1. The Paste Reader opens /p/AbC123/raw, either directly, from the Raw button on the paste page, or from the Raw button on the Paste Created state.
  2. The backend looks up the slug with a parameterized query and checks expires_at.
  3. If the slug does not exist, the response is 404 Paste Not Found.
  4. If the paste has expired, the response is Paste Expired.
  5. If the paste is live, the response carries Content-Type: text/plain; charset=utf-8 and the body is the paste content only — no navbar, no HTML UI, no buttons, no footer, no CSS, and no JSON wrapper.
  6. A paste containing print("Hello World") returns exactly print("Hello World"), never {"text": "print(\"Hello World\")"}.
  7. Continuation: the reader pipes the output into another tool, saves it, or returns to the paste page.
Page 17 of 26

Flow 5 — Paste Reader downloads the paste

  1. From the paste page, the Paste Reader presses Download.
  2. The browser downloads the paste content as a text file.
  3. The file is named paste-ID.txt, or script.lua, script.js, script.py, or script.txt when the creator selected a language.
  4. The downloaded file contains the exact stored content.
  5. Recovery: if the download cannot start, the reader can still use Copy or Raw to obtain the content.
  6. Continuation: the reader continues reading or leaves the page.

Flow 6 — Paste Reader copies the paste

  1. From the paste page, the Paste Reader presses Copy.
  2. The paste content is written to the clipboard.
  3. On success, Copied! is shown.
  4. Recovery: if the clipboard write is unavailable, the content remains visible and selectable in the viewer so the reader can copy it manually.
  5. Continuation: the reader pastes the content into another application.

Flow 7 — API consumer creates and retrieves a paste

  1. An API consumer sends POST /api/pastes with {"title": "My Paste", "content": "Hello World", "language": "text", "visibility": "public", "expiration": null}.
  2. The backend validates the input, enforces the size limit and rate limit, generates a unique random slug, and stores the record.
  3. The response is {"success": true, "id": "AbC123", "url": "/p/AbC123", "raw_url": "/p/AbC123/raw"}.
  4. The consumer sends GET /api/pastes/AbC123 and receives the paste information.
  5. The consumer sends GET /api/pastes/AbC123/raw and receives Content-Type: text/plain; charset=utf-8 with the paste body only.
  6. Recovery: an unknown ID returns 404 Paste Not Found; an expired paste returns Paste Expired; a server failure returns Something went wrong. Please try again.
  7. Continuation: the consumer uses the returned URLs or data in its own workflow.
Page 18 of 26

Flow 8 — Paste Creator chooses expiration and visibility

  1. In the composer, the Paste Creator opens the Expiration cell and selects one of Never, 10 Minutes, 1 Hour, 1 Day, 7 Days, or 30 Days. The default is Never.
  2. The creator opens the Visibility cell and selects Public or Unlisted. The default is Public.
  3. The creator submits the paste.
  4. The backend stores expires_at computed from the chosen expiration and stores the chosen visibility.
  5. If the creator chose Unlisted, the paste remains openable via its direct URL but does not appear in any listing or search surface.
  6. If the creator chose Public, the paste can be found if a search system exists.
  7. After the expiry moment, any attempt to open the paste page, the raw route, or either API endpoint returns Paste Expired because the backend genuinely checks expiration.
  8. Continuation: the creator shares the direct URL, and the reader experiences the paste as available or expired accordingly.

Flow 9 — Paste Reader encounters an error and recovers

  1. The Paste Reader opens a paste URL that does not exist and sees 404 Paste Not Found.
  2. Alternatively, the reader opens an expired paste and sees Paste Expired.
  3. Alternatively, a server failure produces Something went wrong. Please try again.
  4. Recovery: the reader uses Back to return to the previous context or navigates Home from the navbar.
  5. Continuation: the reader creates a new paste or opens a different paste URL.

Flow 10 — Operator runs and deploys the system

  1. The operator installs the project using the documented folder structure: server.js, package.json, database/database.sqlite, public/index.html, public/style.css, public/app.js, and README.md.
  2. The operator installs dependencies from package.json and starts the Node.js + Express server with the SQLite database.
  3. The operator follows the README to run the system on localhost and confirms that /p/:id and /p/:id/raw genuinely work.
  4. The operator deploys to hosting and sets the base URL environment variable to the production domain, for example https://paste.example.com.
  5. The operator connects the custom domain so that paste URLs resolve as https://paste.example.com/p/AbC123 and raw URLs as https://paste.example.com/p/AbC123/raw.
  6. The operator enables HTTPS in production.
  7. Recovery: if the base URL is unset, the system falls back to a documented default rather than a hardcoded localhost.
  8. Continuation: the operator serves traffic on the custom domain.
Page 19 of 26

6. Visuals Colors and Theme

The creative direction is authoritative for this section: Systematic clarity after Massimo Vignelli — a paste bin built like a subway map. The muse is Massimo Vignelli; the headline idea is that a paste host is an information system, so slugs are identifiers, endpoints are routes, expiration is a schedule, visibility is an access level, and raw mode is a clean data line. Dark mode is the default, so Vignelli's white ground inverts to a deep ink ground with the same coded primary accents acting as wayfinding.

Colour tokens (dark mode, default)

RoleTokenValue
Background (ink ground, ~70% of screen)--bg#0E0E0F
Surface (composer, code viewer panels)--surface#17181A
Body text (only body colour)--text#F2F0EC
Primary / action code--primary#E4322B
Accent / metadata code--accent#F0C020
Muted / labels, timestamps, helper text--muted#8C8C8C
Hairline rules--rulergba(242, 240, 236, 0.12)

No colour is decorative: every hue means something, exactly as a transit line does. Red means "act or live" — Create Paste, the active nav rule, focus rings, the "LIVE" dot on unexpired pastes. Yellow means "metadata" — expiration chips, language tags, the line-number gutter. Grey means "label" — labels, timestamps, helper text. Contrast: #F2F0EC on #0E0E0F is approximately 17:1; #E4322B on #0E0E0F is approximately 4.6:1, so red is used only for text at 18px or larger or for bold text and never for small body copy.

Page 20 of 26

Typography

  • Headings: Archivo — grotesque, tight, flush-left ragged-right, sentence case for headlines and uppercase for micro-labels at 11px with 0.14em tracking. Headlines sit at 700 weight with -0.02em tracking; labels and buttons at 500. No italic, no decorative display face — hierarchy comes from size, weight, and rules, never from a second family.
  • Body: Archivo.
  • Scale: 1.5 modular for display, 1.25 for UI — 88 / 64 / 40 / 28 / 20 / 16 / 14 / 13 / 11. Hero headline clamp(40px, 7vw, 88px). Section headings 40px desktop / 28px mobile. Body 16px, small 14px, labels 11px uppercase, code 13.5px with 1.65 line-height.
  • Numerals: always tabular in metadata rows.
  • Monospace: used for all paste content and the composer textarea.

Shape language

Hard-edged and rectilinear. Radius is 0 on section blocks, 2px on inputs and buttons, and 0 on the code viewer (it is a data surface, not a card). Structure comes from 1px rules and negative space, not from shadows: one hairline border per panel, one 3px colour bar as a section marker. No pills, no blobs, no soft continuous curves.

Layout

A strict 12-column grid with 24px gutters and a 1280px max content width; every element snaps to it. The homepage reads top-to-bottom as a route: nav rule, oversized headline block, then a two-zone composer — a full-width title input on its own row, a 60vh monospace textarea as the dominant element, and a three-cell metadata row (Expiration / Visibility / Syntax) aligned as label-over-value pairs with hard vertical rules between cells, with the CTA pinned flush right beneath. The paste view is a document, not a card: a metadata header of ruled label/value rows (ID, created, language, visibility, expiry), then the code viewer with a fixed line-number gutter, then a ruled action bar. Mobile collapses the 12 columns to 4 with the metadata cells stacking full width and the textarea filling the viewport width edge to edge.

Page 21 of 26

Imagery

Diagrammatic, not photographic. The visual vocabulary is a route map: a small SVG system diagram on the About section showing POST /api/pastes → slug → /p/:id → /p/:id/raw as connected nodes on a grid, plus pictogram icons for copy, raw, download, fullscreen, and search drawn at 1.5px stroke. No stock photography, no 3D renders, no illustration for its own sake. The paste content itself, set in monospace with a ruled gutter, is the largest image on the site.

7. Signature Design Concept

The public entry is an information poster, not a SaaS hero. The first screen is composed as a visible grid you can see, and it is built entirely from accepted content and controls.

  • Left two-thirds: a flush-left headline set at clamp(40px, 7vw, 88px) in Archivo 700, three lines deep, reading PASTE IT. / SHARE THE SLUG. / READ IT RAW. sitting directly on the ink ground with no card behind it.
  • Immediately beneath the headline: a single 4px red rule spanning the full grid width, which doubles as the composer's top edge — the signature move that ties the poster to the tool.
  • Right third: a stacked metadata column of ruled label/value rows in 11px uppercase — ENDPOINT /api/pastes, RAW /p/:id/raw, EXPIRY NEVER → 30D, VISIBILITY PUBLIC / UNLISTED — each row separated by a hairline, with the yellow LIVE chip on the first row.
  • Below the rule: the composer begins in-frame — title input, then the 60vh monospace textarea with the placeholder Paste your text here..., then the three ruled metadata cells and the red Create Paste button flush right.
  • No gradient, no blob, no centred stack, no illustration — the composition is a grid you can see, and the paste content itself is the largest image on the site.

The same red rule re-sweeps once across the screen as the Create Paste success transition, so the poster's structural line becomes the product's confirmation gesture.

Page 22 of 26

8. Interaction Model & Motion Direction

Interaction Model: Static Motion Tempo: still Hero Dimensionality: flat

The direction's tempo is still and its hero dimensionality is flat, so the interaction model is copied as Static (direction) — minimal and mechanical, with no easing theatrics, no bounce, and no floating panels.

Landing Hero Motion Brief

  • Focal subject: the flush-left three-line headline PASTE IT. / SHARE THE SLUG. / READ IT RAW. on the ink ground, with the 4px red rule beneath it and the ruled metadata column at right.
  • Input → transformation → outcome thesis: the creator's typed or pasted text enters the 60vh monospace textarea → pressing Create Paste sends it to the backend, which generates a unique slug and stores the record → the red rule sweeps once across the top rule and the Paste Created state presents the working URL with Copy Link, Open Paste, and Raw. Only accepted behaviour is used.
  • Motion vocabulary: 120ms linear state changes on hover, focus, and copy confirmation; one purposeful loop only — the caret in the composer and a 1.2s fade-in of the code viewer on load; colour-block wipes reserved for the Create Paste success transition, where the red bar sweeps across the top rule once.
  • Composed first frame: nav rule at top; oversized headline flush left; the 4px red rule spanning the grid; the ruled metadata column at right with the yellow LIVE chip on the first row; the composer in-frame below with the title input, the monospace textarea showing Paste your text here..., the three ruled metadata cells, and the red Create Paste button flush right.
  • Reduced-motion state: everything stops under prefers-reduced-motion; the caret loop, the 1.2s viewer fade-in, and the red rule sweep are all suppressed, and every hover-only reveal has a visible static equivalent.
Page 23 of 26

9. Non-Functional Requirements

NFR-1 — Security (explicit) Escape HTML in content; never execute stored JavaScript; use parameterized queries; validate input; rate limit paste creation; limit paste size; prevent path traversal; use random IDs; protect the API from spam; do not store passwords or sensitive data by default; use HTTPS in production. Paste content must be treated as data, not as executable HTML. Rationale: the source states these as mandatory ("Wajib") for a publicly reachable paste host.

NFR-2 — Expiration enforcement (explicit) The backend must genuinely check expiration on every retrieval path rather than only hiding expired pastes in the frontend. Rationale: the source explicitly requires backend enforcement.

NFR-3 — Visibility semantics (explicit) Unlisted pastes must remain accessible via direct URL while not appearing in listings or search; Public pastes may be found if a search system exists. Rationale: the source defines these semantics as a hard constraint.

NFR-4 — Content fidelity (explicit) Paste content must never be altered when displayed or syntax highlighted; the raw endpoint must return the body only, with no navbar, HTML UI, buttons, footer, CSS, or JSON wrapper. Rationale: the source requires exact reproduction and a clean data line.

NFR-5 — Slug unpredictability (explicit) IDs/slugs must be unique and hard to guess; sequential IDs such as /p/1, /p/2, /p/3 are not allowed. Rationale: the source states this as a hard constraint.

NFR-6 — Deployment portability (explicit) Do not hardcode localhost; use an environment variable for the base URL so the system works on a custom domain such as https://paste.example.com. Rationale: the source requires custom-domain support.

NFR-7 — Real persistence (explicit) The site must not be frontend-only; a real backend that stores data is required, and localStorage must not be used as the primary database. Rationale: the source states this as a hard constraint.

NFR-8 — Responsive and mobile-friendly (explicit) The site must be comfortable on Android: the textarea fills the screen width, buttons are not too small, the navbar becomes a mobile menu on small screens, and the paste viewer supports horizontal scroll. Rationale: the source requires mobile comfort.

NFR-9 — Performance and readability of long pastes (explicit) The paste viewer must remain comfortable for reading long text, with line numbers, horizontal and vertical scrolling, search, and fullscreen. Rationale: the source requires a comfortable long-text reader.

NFR-10 — Complete, runnable deliverables (explicit) Deliver complete ready-to-run source code, not partial snippets, covering all seventeen listed areas including localhost instructions, hosting deployment, and custom-domain connection. Rationale: the source explicitly forbids snippets.

NFR-11 — Accessibility of motion and contrast (required_inference) All motion must stop under prefers-reduced-motion, and the palette must maintain the stated contrast ratios (#F2F0EC on #0E0E0F ≈ 17:1; #E4322B on #0E0E0F ≈ 4.6:1, so red is used only at ≥18px or bold). Rationale: required to make the accepted motion and colour direction usable without harming readability.

Page 24 of 26

10. Tech Stack

Source-specified choices are preserved exactly:

  • Frontend: HTML + CSS + JavaScript.
  • Backend: Node.js + Express.
  • Database: SQLite for the simple version.
  • Folder structure: project/ containing server.js, package.json, database/database.sqlite, public/index.html, public/style.css, public/app.js, and README.md.
  • Configuration: base URL supplied through an environment variable; no hardcoded localhost.
  • Production: HTTPS required.

The source permits other frameworks provided the whole system remains easy to run, but the documented stack above is the accepted default and is what the deliverables describe.

Page 25 of 26

11. Assumptions and Constraints

Assumptions

  1. (Assumption) The application is anonymous: no accounts, login, or user identity are in scope, consistent with the source's instruction not to store passwords or sensitive data by default.
  2. (Assumption) Paste URLs are the access mechanism; anyone holding a URL can read the paste, and Unlisted pastes are reachable only by direct URL.
  3. (Assumption) The phrase "if a search system exists" in the visibility rule is conditional and does not itself require building a search or listing surface.
  4. (Assumption) A single SQLite database file is sufficient for the simple version, as the source specifies.
  5. (Assumption) The paste size limit and rate-limit thresholds are implementation parameters chosen at build time; the source requires that both exist but does not fix their values.
  6. (Assumption) The base URL environment variable has a documented fallback so that local development works without a hardcoded localhost.

Constraints

  1. (Explicit) Paste content must be treated as data, never executed as HTML or JavaScript; HTML escaping is mandatory.
  2. (Explicit) Do not use sequential IDs such as /p/1, /p/2, /p/3; IDs/slugs must be unique and hard to guess.
  3. (Explicit) The raw endpoint must not include navbar, HTML UI, buttons, footer, CSS, or a JSON wrapper.
  4. (Explicit) Do not hardcode localhost; use an environment variable for the base URL.
  5. (Explicit) Do not build a frontend-only site; a real backend that stores data is required.
  6. (Explicit) Do not use localStorage as the primary database.
  7. (Explicit) Do not create fake links or mockups; /p/:id and /p/:id/raw must genuinely work.
  8. (Explicit) Do not store passwords or sensitive data by default.
  9. (Explicit) Do not alter the original paste content when displaying or syntax highlighting it.
  10. (Explicit) Use parameterized queries, input validation, rate limiting on paste creation, paste size limits, path traversal prevention, random IDs, API spam protection, and HTTPS in production.
  11. (Explicit) Unlisted pastes must not appear in listings/search while remaining accessible via direct URL.
  12. (Explicit) Expiration must be enforced by the backend, not only hidden in the frontend.
  13. (Explicit) Deliver complete ready-to-run source code, not partial snippets.
  14. (Explicit) The design and branding must be original; Pastefy.com is a conceptual and user-flow reference only, and its names, copy, features, and facts must not become project content.
  15. (Explicit) The generic indigo/blue-on-white SaaS template look is forbidden for this project.
Page 26 of 26

12. Glossary

  • Paste — A stored text record with id, slug, title, content, language, visibility, created_at, and expires_at.
  • Slug — The unique, hard-to-guess identifier in a paste URL, for example AbC123 in /p/AbC123.
  • Raw mode — The /p/:id/raw endpoint that returns only the paste body with Content-Type: text/plain; charset=utf-8 and no UI or wrapper.
  • Expiration — The time limit chosen at creation from Never, 10 Minutes, 1 Hour, 1 Day, 7 Days, or 30 Days, stored as expires_at and enforced by the backend.
  • Visibility — Public or Unlisted. Public pastes can be found if a search system exists; Unlisted pastes are reachable only by direct URL and never appear in listings or search.
  • Composer — The homepage/Create Paste form consisting of the title input, the large monospace textarea, the Expiration / Visibility / Syntax metadata cells, and the Create Paste button.
  • Code viewer — The paste reading surface with a fixed yellow line-number gutter, horizontal and vertical scrolling, search, fullscreen, Copy, Raw, and Download.
  • Wayfinding colour code — The rule that red means "act or live", yellow means "metadata", and grey means "label".
  • Base URL — The environment-variable-configured origin used to build absolute paste and raw URLs, for example https://paste.example.com.

No completed page designs yet.

Completed design pages will appear here when they are ready to preview.

Home: Open homepage
Home: 1. Paste text into textarea
Home: 2. Set title and language
Home: 3. Choose expiration
Home: 4. Choose visibility
Home: 5. Press Create Paste
Home: 6. See empty content error
Home: 7. See server error, retry
/api/pastes: 8. POST paste to endpoint
Paste Created: See created confirmation
Paste Created: Copy Link
/p/:id: Open Paste
/p/:id/raw: Open Raw
Create Paste: Open Create Paste destination
Create Paste: 1. Enter content and metadata
Create Paste: 2. Submit paste
Create Paste: 3. See empty content error
Create Paste: 4. See server error, draft kept
/api/pastes: 5. POST paste to endpoint
About: Read product explanation
/p/:id: Read own paste in viewer
/p/:id: Copy content
/p/:id: Download content
About: Follow route map

No completed page designs yet.

Completed design pages will appear here when they are ready to preview.

Home: Open homepage
Home: 1. Paste text into textarea
Home: 2. Set title and language
Home: 3. Choose expiration
Home: 4. Choose visibility
Home: 5. Press Create Paste
Home: 6. See empty content error
Home: 7. See server error, retry
/api/pastes: 8. POST paste to endpoint
Paste Created: See created confirmation
Paste Created: Copy Link
/p/:id: Open Paste
/p/:id/raw: Open Raw
Create Paste: Open Create Paste destination
Create Paste: 1. Enter content and metadata
Create Paste: 2. Submit paste
Create Paste: 3. See empty content error
Create Paste: 4. See server error, draft kept
/api/pastes: 5. POST paste to endpoint
About: Read product explanation
/p/:id: Read own paste in viewer
/p/:id: Copy content
/p/:id: Download content
About: Follow route map