RepoPilotOpen in app →

kkuchta/css-only-chat

A truly monstrous async web chat using no JS whatsoever on the frontend

Mixed

Stale — last commit 3y ago

worst of 4 axes
Use as dependencyMixed

last commit was 3y ago; no tests detected…

Fork & modifyMixed

no tests detected; no CI workflows detected…

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isMixed

last commit was 3y ago; no CI workflows detected

  • 5 active contributors
  • MIT licensed
  • Stale — last commit 3y ago
Show 3 more →
  • Concentrated ownership — top contributor handles 68% of recent commits
  • No CI workflows detected
  • No test directory detected
What would change the summary?
  • Use as dependency MixedHealthy if: 1 commit in the last 365 days; add a test suite
  • Fork & modify MixedHealthy if: add a test suite
  • Deploy as-is MixedHealthy if: 1 commit in the last 180 days

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Great to learn from" badge

Paste into your README — live-updates from the latest cached analysis.

RepoPilot: Great to learn from
[![RepoPilot: Great to learn from](https://repopilot.app/api/badge/kkuchta/css-only-chat?axis=learn)](https://repopilot.app/r/kkuchta/css-only-chat)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/kkuchta/css-only-chat on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: kkuchta/css-only-chat

Generated by RepoPilot · 2026-05-10 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/kkuchta/css-only-chat shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

WAIT — Stale — last commit 3y ago

  • 5 active contributors
  • MIT licensed
  • ⚠ Stale — last commit 3y ago
  • ⚠ Concentrated ownership — top contributor handles 68% of recent commits
  • ⚠ No CI workflows detected
  • ⚠ No test directory detected

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live kkuchta/css-only-chat repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/kkuchta/css-only-chat.

What it runs against: a local clone of kkuchta/css-only-chat — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in kkuchta/css-only-chat | Confirms the artifact applies here, not a fork | | 2 | License is still MIT | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 1005 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>kkuchta/css-only-chat</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of kkuchta/css-only-chat. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/kkuchta/css-only-chat.git
#   cd css-only-chat
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of kkuchta/css-only-chat and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "kkuchta/css-only-chat(\\.git)?\\b" \\
  && ok "origin remote is kkuchta/css-only-chat" \\
  || miss "origin remote is not kkuchta/css-only-chat (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
  && ok "license is MIT" \\
  || miss "license drift — was MIT at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "server.rb" \\
  && ok "server.rb" \\
  || miss "missing critical file: server.rb"
test -f "style.css" \\
  && ok "style.css" \\
  || miss "missing critical file: style.css"
test -f "config.ru" \\
  && ok "config.ru" \\
  || miss "missing critical file: config.ru"
test -f "README.md" \\
  && ok "README.md" \\
  || miss "missing critical file: README.md"
test -f "Gemfile" \\
  && ok "Gemfile" \\
  || miss "missing critical file: Gemfile"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 1005 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~975d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/kkuchta/css-only-chat"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

css-only-chat is an asynchronous web chat application that sends and receives messages in real-time with zero JavaScript on the frontend. It exploits CSS pseudo-selectors (:active) to detect button presses as image requests and uses HTTP chunked transfer encoding to push updates via streaming HTML, enabling persistent two-way communication using only CSS and HTML. Minimal monolith: single server.rb file handles all backend routing and HTTP chunked streaming, style.css applies the CSS pseudo-selector tricks, and the index page (generated dynamically by server.rb) streams HTML indefinitely to clients. No separation of concerns or modular structure—everything is inline in the server loop.

👥Who it's for

Web developers and browser enthusiasts exploring the limits of CSS and HTTP, specifically those interested in understanding pre-WebSocket async patterns and creative constraint-based problem solving. It serves as an educational proof-of-concept rather than a production chat system.

🌱Maturity & risk

This is an experimental/novelty project. The codebase is minimal (8.5KB Ruby, 124B CSS) with no test suite, CI/CD, or versioning strategy visible. With no recent commit history provided and the 'monstrous' characterization in the description, this is a clever demonstration project rather than a maintained production tool.

High risk for production use: single-file Ruby backend (server.rb) with likely zero error handling, no dependency management visible beyond Gemfile, and the architectural pattern (infinite streaming HTML) is fundamentally at odds with modern web standards and scalability. The novelty nature means it lacks the robustness, monitoring, and operational tooling real applications need.

Active areas of work

No commit history is visible in the provided data. The repository appears stable as a static proof-of-concept rather than actively developed; likely no ongoing work, open PRs, or issues.

🚀Get running

git clone https://github.com/kkuchta/css-only-chat.git
cd css-only-chat
bundle install
rackup config.ru

Then open http://localhost:9292 in your browser.

Daily commands:

bundle install
rackup config.ru

Server listens on http://localhost:9292 by default (configurable in config.ru).

🗺️Map of the codebase

  • server.rb — The Rack application entry point that handles all HTTP requests, message storage, and the comet-style long-polling response mechanism.
  • style.css — The entire frontend—uses CSS pseudo-selectors and background-image tricks to detect button presses and receive messages without JavaScript.
  • config.ru — Rack configuration file that boots the server; must be understood to deploy or run the application locally.
  • README.md — Explains the architectural innovation (CSS-only async chat via background-images and comet) that makes this codebase conceptually unique.
  • Gemfile — Declares all Ruby dependencies; essential to understand what libraries enable the comet long-polling and Rack setup.

🧩Components & responsibilities

  • server.rb (Rack application) (Ruby, Rack) — Handles all HTTP endpoints, manages message queue/storage, streams responses via comet long-polling, serves initial HTML.
    • Failure mode: Server crash loses all in-memory messages; unresponsive long-polling leaves browser hung waiting for updates.
  • style.css (Frontend logic) (CSS pseudo-selectors, CSS content property) — Detects button presses via :active, triggers background-image loads, injects received messages into the DOM via ::before/::after content.
    • Failure mode: CSS parsing error breaks interaction; browser cache causes duplicate/missed messages if URLs aren't unique.
  • config.ru (Boot loader) (Rack) — Loads the Rack app and starts the server process.
    • Failure mode: Configuration syntax error prevents server from starting.

🔀Data flow

  • Browser (button click)server.rb — User clicks button; CSS :active triggers background-image URL load; HTTP GET request with message data in query string.
  • server.rbMessage Store (in-memory) — Server receives request, parses message, stores it in memory.
  • Message Storeserver.rb (long-polling response) — Server polls store, retrieves pending messages, encodes them as background-image URLs in streamed HTML.
  • server.rb responseBrowser — Server sends HTML with background-image URLs; browser loads images, CSS injects message text via ::before/::after content.

🛠️How to make changes

Add a new message type or action

  1. Define a new route in server.rb that stores or processes the message (e.g., POST /send?msg=...) (server.rb)
  2. Add a CSS rule in style.css using :active pseudo-selector that triggers a background-image URL pointing to the new route (style.css)
  3. Add the corresponding HTML button element in the response served by server.rb that the CSS rule targets (server.rb)

Modify the message receive mechanism

  1. Edit server.rb to change how the comet long-polling response generates inline background-image URLs that encode pending messages (server.rb)
  2. Update style.css selectors if the message injection points or pseudo-classes change (style.css)

Add a new external dependency

  1. Add the gem to Gemfile with desired version constraints (Gemfile)
  2. Run bundle install and commit Gemfile.lock (Gemfile.lock)
  3. Require and use the gem in server.rb (server.rb)

🔧Why these technologies

  • Ruby + Rack — Lightweight server framework sufficient for HTTP request handling and long-polling; minimal overhead for a proof-of-concept chat.
  • CSS pseudo-selectors (:active) — Enables button-press detection without JavaScript; lazy-loads background images only when pseudo-selector matches.
  • Comet (long-polling) — Server holds HTTP connection open to stream messages back to browser; simulates real-time bidirectional communication.
  • Background-image URLs as data transport — Allows encoding both requests (send data) and responses (receive messages) via URL manipulation; browsers load images even from CSS.

⚖️Trade-offs already made

  • No JavaScript on frontend

    • Why: Demonstrates that async messaging is theoretically possible without client-side code; a creative engineering challenge.
    • Consequence: Interaction is limited to button clicks; no validation, error handling, or dynamic behavior on the client; severely constrains UX.
  • Comet over WebSocket

    • Why: WebSockets would require JavaScript; comet (HTTP long-polling) works with pure HTML/CSS.
    • Consequence: Higher latency, greater server resource usage (keeps many connections open), less efficient than true push technologies.
  • In-memory message storage (likely in server.rb)

    • Why: Simplicity; avoids database setup complexity for a proof-of-concept.
    • Consequence: Messages lost on server restart; does not scale to multiple processes; no persistence.
  • Styling and interaction logic tightly coupled in CSS

    • Why: No separation possible without JavaScript.
    • Consequence: Hard to modify UI without breaking interaction; little room for accessibility or progressive enhancement.

🚫Non-goals (don't propose these)

  • Does not provide persistent message history across server restarts
  • Does not authenticate users or support private conversations
  • Does not scale horizontally (in-memory state; no shared cache/database)
  • Does not support real-time typing indicators or presence awareness
  • Does not validate user input or sanitize output (XSS-prone)
  • Does not work on browsers without CSS support or with JavaScript disabled for security

📊Code metrics

  • Avg cyclomatic complexity: ~4 — Logic is straightforward (HTTP echo, message queue) but the architecture is conceptually complex (comet + CSS tricks); most complexity is in understanding the novelty rather than implementation.
  • Largest file: server.rb (150 lines)
  • Estimated quality issues: ~7 — Lack of input validation, no error handling, tight coupling, in-memory storage, no logging, no tests, and minimal comments on the CSS hack.

⚠️Anti-patterns to avoid

  • In-memory state as primary storage (High)server.rb: All messages and state stored in process memory; no persistence or sharing across multiple server instances.
  • Tight coupling of transport and presentation (Medium)style.css, server.rb: Message logic, encoding, and display are inseparable; any change to transport requires CSS and server modifications in sync.
  • Lack of input validation or sanitization (High)server.rb: User input is likely passed directly into HTML/CSS; potential for XSS if malicious content is submitted.
  • Browser cache interference (Medium)server.rb: If background-image URLs are not sufficiently unique (cache busting), browser may reuse cached images and miss new messages.

🔥Performance hotspots

  • server.rb (comet long-polling) (Resource exhaustion (file descriptors, memory)) — Server must hold one HTTP connection per connected browser; connection pool is limited; scales poorly beyond ~1000 concurrent users.
  • style.css (message rendering) (Browser memory / DOM thrashing) — All messages rendered via CSS ::before/::after content; no pagination or scrolling; unbounded DOM growth causes browser slowdown.
  • Message Store (in-memory) (Algorithmic complexity) — Linear lookup or poll on every client refresh; no indexing; O(n) performance on number of messages.

🪤Traps & gotchas

No obvious environment variables or runtime dependencies beyond Ruby + Bundler. However: (1) the streaming HTML pattern means clients never finish loading the page—this breaks some browser extensions, caching layers, and load balancers; (2) there is no session management or authentication, so all users share one chat room; (3) the :active pseudo-selector only works on actual clicks, not programmatic focus—no mobile touch support; (4) browser memory grows indefinitely as new HTML chunks are appended.

🏗️Architecture

💡Concepts to learn

  • HTTP Chunked Transfer Encoding — The core transport mechanism in this repo—allows server to keep sending data indefinitely without closing the connection, enabling the streaming HTML push pattern that makes the chat receive updates.
  • CSS Pseudo-Selectors (:active, :visited, :focus) — Exploited to trigger side effects (background-image loads) that send HTTP requests to the server; this is the only 'sending' mechanism available without JavaScript.
  • Comet (Server Push / Long Polling) — The architectural pattern this project implements—keeping an HTTP connection open and streaming data to clients. This predates WebSockets and SSE as the main async web transport.
  • Browser Resource Loading Caching — CSS only loads a background-image URL once per selector match, creating the 'single-use button' problem that the streaming HTML solution must work around.
  • Rack Framework (Ruby Web Server Interface) — The minimal abstraction used in server.rb and config.ru to handle HTTP requests and streaming responses; understanding Rack is essential to modifying the backend.
  • HTML Streaming & Progressive Enhancement — By sending <style> elements mid-stream, the server can hide old HTML and show new messages without JavaScript; this exploits the browser's ability to apply CSS to HTML before page load completes.
  • remy/polyfill — Demonstrates creative browser capability hacks; both projects exploit browser quirks to do something the browser wasn't designed for.
  • jashkenas/coffeescript — Similar era of constraint-based innovation in web development (pre-modern JS tooling); CoffeeScript explored making code simpler within browser limits.
  • websockets/ws — The modern, production-ready replacement for this HTTP chunked streaming pattern; WebSockets solve the same real-time async problem properly.
  • eventsource/eventsource — Server-Sent Events is the modern standard evolution of Comet-style streaming, closer in spirit to css-only-chat's approach than WebSockets.

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add comprehensive Browser Compatibility Tests in CONTRIBUTING.md

This repo relies on cutting-edge CSS selector tricks (pseudo-selectors like :active for comet-style polling) that have varying browser support. New contributors need explicit guidance on which browsers/versions support the core technique. Currently CONTRIBUTING.md likely lacks this, making it hard for contributors to test their changes properly.

  • [ ] Document in CONTRIBUTING.md which CSS pseudo-selectors are used (e.g., :active, background-image timing)
  • [ ] List tested browser versions (Chrome, Firefox, Safari, Edge) with their CSS support levels
  • [ ] Add manual testing steps for the comet polling mechanism across browsers
  • [ ] Reference any known CSS limitations or graceful degradation approaches

Create test_fixtures.rb for server.rb integration tests

The server.rb file likely handles complex async message routing and WebSocket-like behavior via CSS background-image requests. There are probably no automated tests, making it risky for contributors to modify the core message handling logic. Adding even basic integration tests would catch regressions.

  • [ ] Create test_fixtures.rb with helper methods to simulate CSS :active button presses (mocked HTTP requests)
  • [ ] Add tests for message persistence and ordering in server.rb
  • [ ] Test that background-image requests are properly served with correct message payloads
  • [ ] Ensure test suite can be run via 'rake test' (add Rakefile if missing)

Document the style.css Architecture in a STYLING.md guide

style.css is the heart of this unusual chat application—it handles button press detection, visual feedback, and the comet-style long-polling visual indicators. New contributors will struggle to modify or extend styles without understanding the non-traditional architecture. A dedicated guide would lower the barrier to entry.

  • [ ] Create STYLING.md explaining how CSS :active selectors trigger HTTP requests
  • [ ] Document any custom animation or timing tricks used for the forever-loading pattern
  • [ ] Explain how pseudo-selectors and background-image URLs encode/transmit user actions
  • [ ] Add examples of how to add a new button or message display feature

🌿Good first issues

  • Add a test suite to verify that pressing a button triggers the correct image request URL—currently there are zero tests.
  • Document the exact HTTP chunked transfer encoding format used in server.rb with examples, as it's the core innovation but undocumented.
  • Implement a username/room system so multiple independent chats can run on one server without users talking over each other.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • b5a2e38 — Merge pull request #20 from Gabgab2003/patch/double_clientids (kkuchta)
  • d3d0fe3 — Work in kkuchta's changes (invalid-email-address)
  • 806765f — Fixed client_ids not clearing after restart (invalid-email-address)
  • 4f47427 — modified: server.rb (invalid-email-address)
  • 45044b5 — Add Contributing file (kkuchta)
  • fce722d — Update readme to point to Install file (kkuchta)
  • a38f106 — Added install file (kkuchta)
  • 455d720 — Added MIT License (kkuchta)
  • 9d86882 — Merge pull request #10 from OskarDamkjaer/patch-1 (kkuchta)
  • 7526c5f — Minor spell-fixes (OskarDamkjaer)

🔒Security observations

The application employs an extremely unconventional architecture using CSS for data transmission, which presents significant security challenges. Critical concerns include potential XSS vulnerabilities, missing security headers, and CSRF protection gaps. The lack of traditional input validation/output encoding mechanisms creates elevated risk. Dependency security cannot be fully assessed without Gemfile content. The novelty of the approach makes it difficult to audit and maintain securely. Recommend security hardening through robust input validation, CSP headers, CSRF tokens, and consideration of migrating to a more conventional, auditable architecture.

  • High · Potential XSS via CSS-based Data Exfiltration — server.rb, style.css. The application uses background-image URLs loaded via CSS pseudo-selectors to transmit user input (button presses). If user-generated content is reflected in these URLs without proper sanitization, attackers could inject malicious payloads. Additionally, the Comet-style long-polling mechanism may transmit unsanitized data back to clients. Fix: Implement strict input validation and output encoding for all user inputs. Use Content Security Policy (CSP) headers to restrict background-image sources. Sanitize all data before embedding in URLs or CSS.
  • High · Missing Security Headers — server.rb, config.ru. No indication of security headers (CSP, X-Frame-Options, X-Content-Type-Options, etc.) in the provided configuration. This leaves the application vulnerable to clickjacking, MIME-type sniffing, and other header-based attacks. Fix: Implement security headers: Content-Security-Policy, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Strict-Transport-Security, and X-XSS-Protection.
  • Medium · Dependency Vulnerabilities Not Assessed — Gemfile, Gemfile.lock. Gemfile and Gemfile.lock exist but content was not provided. Ruby dependencies may contain known vulnerabilities. Without version pinning details, the application could be running outdated or vulnerable gems. Fix: Review Gemfile for outdated dependencies using 'bundle audit'. Keep Ruby and all gems updated. Use exact version pinning for production dependencies.
  • Medium · Potential CSRF via URL-based Actions — server.rb. The application uses URL-based data transmission via CSS (background-image loading). Without CSRF token validation, an attacker could craft malicious CSS selectors or websites that trigger unwanted message sends on behalf of authenticated users. Fix: Implement CSRF token validation for all state-changing operations. Validate token on server-side for any action that modifies chat state.
  • Medium · Information Disclosure via Long-Polling — server.rb. The forever-loading index page (Comet-style long-polling) may expose timing information or response patterns that could leak data about active users, message frequency, or application state to network observers. Fix: Use randomized response delays, implement rate limiting, and add noisy padding to responses. Consider using WebSockets over TLS instead of long-polling.
  • Low · Unconventional Architecture Increases Attack Surface — style.css, server.rb. Using CSS pseudo-selectors to transmit data is highly unconventional and may introduce subtle security flaws in message handling, URL encoding, and state management that are difficult to audit and maintain securely. Fix: Consider implementing a more conventional architecture with JavaScript that uses secure, well-tested libraries. If CSS-only approach must continue, implement rigorous security testing and code review processes.
  • Low · Missing Rate Limiting — server.rb. No evidence of rate limiting on message submission endpoints, which could allow spam or denial-of-service attacks via rapid message injection through CSS selectors. Fix: Implement rate limiting per user/IP address. Add CAPTCHAs for repeated rapid submissions. Monitor for abuse patterns.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Mixed signals · kkuchta/css-only-chat — RepoPilot