forked from genewildish/Mainline
155 lines
4.3 KiB
Markdown
155 lines
4.3 KiB
Markdown
# 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, `<module>` 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 → `<module>`
|
|
|
|
**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 `<module>` 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
|