diff --git a/docs/superpowers/specs/2026-03-16-code-scroll-design.md b/docs/superpowers/specs/2026-03-16-code-scroll-design.md new file mode 100644 index 0000000..719db19 --- /dev/null +++ b/docs/superpowers/specs/2026-03-16-code-scroll-design.md @@ -0,0 +1,154 @@ +# Code Scroll Mode — Design Spec + +**Date:** 2026-03-16 +**Branch:** feat/code-scroll +**Status:** Approved + +--- + +## Overview + +Add a `--code` CLI flag that puts MAINLINE into "source consciousness" mode. Instead of RSS headlines or poetry stanzas, the program's own source code scrolls upward as large OTF half-block characters with the standard white-hot → deep green gradient. Each scroll item is one non-blank, non-comment line from `engine/*.py`, attributed to its enclosing function/class scope and dotted module path. + +--- + +## Goals + +- Mirror the existing `--poetry` mode pattern as closely as possible +- Zero new runtime dependencies (stdlib `ast` and `pathlib` only) +- No changes to `scroll.py` or the render pipeline +- The item tuple shape `(text, src, ts)` is unchanged + +--- + +## New Files + +### `engine/fetch_code.py` + +Single public function `fetch_code()` that returns `(items, line_count, 0)`. + +**Algorithm:** + +1. Glob `engine/*.py` in sorted order +2. For each file: + a. Read source text + b. `ast.parse(source)` → build a `{line_number: scope_label}` map by walking all `FunctionDef`, `AsyncFunctionDef`, and `ClassDef` nodes. Each node covers its full line range. Inner scopes override outer ones. + c. Iterate source lines (1-indexed). Skip if: + - The stripped line is empty + - The stripped line starts with `#` + d. For each kept line emit: + - `text` = `line.rstrip()` (preserve indentation for readability in the big render) + - `src` = scope label from the AST map, e.g. `stream()` for functions, `MicMonitor` for classes, `` for top-level lines + - `ts` = dotted module path derived from filename, e.g. `engine/scroll.py` → `engine.scroll` +3. Return `(items, len(items), 0)` + +**Scope label rules:** +- `FunctionDef` / `AsyncFunctionDef` → `name()` +- `ClassDef` → `name` (no parens) +- No enclosing node → `` + +**Dependencies:** `ast`, `pathlib` — stdlib only. + +--- + +## Modified Files + +### `engine/config.py` + +Extend `MODE` detection to recognise `--code`: + +```python +MODE = ( + "poetry" if "--poetry" in sys.argv or "-p" in sys.argv + else "code" if "--code" in sys.argv + else "news" +) +``` + +### `engine/app.py` + +**Subtitle line** — extend the subtitle dict: + +```python +_subtitle = { + "poetry": "literary consciousness stream", + "code": "source consciousness stream", +}.get(config.MODE, "digital consciousness stream") +``` + +**Boot sequence** — add `elif config.MODE == "code":` branch after the poetry branch: + +```python +elif config.MODE == "code": + from engine.fetch_code import fetch_code + slow_print(" > INITIALIZING SOURCE ARRAY...\n") + time.sleep(0.2) + print() + items, line_count, _ = fetch_code() + print() + print(f" {G_DIM}>{RST} {G_MID}{line_count} LINES ACQUIRED{RST}") +``` + +No cache save/load — local source files are read instantly and change only on disk writes. + +--- + +## Data Flow + +``` +engine/*.py (sorted) + │ + ▼ +fetch_code() + │ ast.parse → scope map + │ filter blank + comment lines + │ emit (line, scope(), engine.module) + ▼ +items: List[Tuple[str, str, str]] + │ + ▼ +stream(items, ntfy, mic) ← unchanged + │ + ▼ +next_headline() shuffles + recycles automatically +``` + +--- + +## Error Handling + +- If a file fails to `ast.parse` (malformed source), fall back to `` scope for all lines in that file — do not crash. +- If `engine/` contains no `.py` files (shouldn't happen in practice), `fetch_code()` returns an empty list; `app.py`'s existing `if not items:` guard handles this. + +--- + +## Testing + +New file: `tests/test_fetch_code.py` + +| Test | Assertion | +|------|-----------| +| `test_items_are_tuples` | Every item from `fetch_code()` is a 3-tuple of strings | +| `test_blank_and_comment_lines_excluded` | No item text is empty; no item text (stripped) starts with `#` | +| `test_module_path_format` | Every `ts` field matches pattern `engine\.\w+` | + +No mocking — tests read the real engine source files, keeping them honest against actual content. + +--- + +## CLI + +```bash +python3 mainline.py --code # source consciousness mode +uv run mainline.py --code +``` + +Compatible with all existing flags (`--no-font-picker`, `--font-file`, `--firehose`, etc.). + +--- + +## Out of Scope + +- Syntax highlighting / token-aware coloring (can be added later) +- `--code-dir` flag for pointing at arbitrary directories (YAGNI) +- Caching code items to disk