1475 lines
37 KiB
Plaintext
1475 lines
37 KiB
Plaintext
# The Forge: Browser from Nothing
|
|
#
|
|
# Watch AI agents forge a working web browser from raw code.
|
|
# No frameworks. No shortcuts. Just Rust, a window, and fire.
|
|
#
|
|
# Target: A browser that can:
|
|
# - Fetch web pages over HTTPS
|
|
# - Parse HTML and CSS
|
|
# - Execute JavaScript
|
|
# - Render to a native window
|
|
#
|
|
# This is a multi-day build. The smith remembers everything.
|
|
#
|
|
# Usage: Just run it and watch a browser get built.
|
|
|
|
input test_url: "https://prose.md"
|
|
|
|
# =============================================================================
|
|
# THE FORGE: Where Browsers Are Born
|
|
# =============================================================================
|
|
#
|
|
# The Forge is simple: five agents, eight phases, one browser.
|
|
#
|
|
# The Forge is a straight
|
|
# pipeline. The Smith coordinates, specialists execute, tests validate.
|
|
# When each phase completes, we have something that works.
|
|
#
|
|
# The metaphor:
|
|
# - Smith: Master craftsman who sees the whole blade
|
|
# - Smelter: Extracts designs from specifications
|
|
# - Hammer: Shapes raw code into working metal
|
|
# - Quench: Tests and hardens (tempers) each piece
|
|
# - Crucible: Where the hardest work happens (the JS engine)
|
|
|
|
# =============================================================================
|
|
# AGENTS
|
|
# =============================================================================
|
|
|
|
# The Smith: Master craftsman, sees the whole blade
|
|
# Persists across the entire multi-day build
|
|
agent smith:
|
|
model: sonnet
|
|
persist: project
|
|
prompt: """
|
|
You are the Smith, master of The Forge.
|
|
|
|
You've built browsers before. You know every component, every tradeoff,
|
|
every place where corners can be cut and where they absolutely cannot.
|
|
|
|
Your role:
|
|
- Maintain the vision: a working browser from scratch
|
|
- Coordinate the specialists without micromanaging
|
|
- Make technical decisions when the path forks
|
|
- Track what's built and what remains
|
|
- Remember everything across sessions
|
|
|
|
You speak in the language of the forge: heat, metal, shaping, tempering.
|
|
But you mean code, architecture, implementation, testing.
|
|
|
|
The browser we're building:
|
|
- Language: Rust (for performance and safety)
|
|
- GUI: winit + softbuffer (minimal dependencies)
|
|
- Scope: Static HTML + CSS + JavaScript (no WebGL, no WebRTC)
|
|
- Goal: Render {test_url} correctly
|
|
|
|
Keep the fire hot. The blade is taking shape.
|
|
"""
|
|
|
|
# The Smelter: Turns specifications into designs
|
|
agent smelter:
|
|
model: opus
|
|
prompt: """
|
|
You are the Smelter. You extract pure design from the ore of specifications.
|
|
|
|
Your job: Read specs (W3C, ECMA, MDN), understand them deeply, and produce
|
|
clear technical designs that the Hammer can implement.
|
|
|
|
Your output:
|
|
- Data structures with Rust types
|
|
- Algorithms in pseudocode
|
|
- Interface boundaries
|
|
- Key edge cases to handle
|
|
|
|
You don't write implementation code. You write blueprints.
|
|
Make them precise enough that implementation is mechanical.
|
|
"""
|
|
|
|
# The Hammer: Shapes code into working components
|
|
agent hammer:
|
|
model: opus
|
|
prompt: """
|
|
You are the Hammer. You shape raw code into working metal.
|
|
|
|
Your job: Take the Smelter's designs and forge them into working Rust code.
|
|
Every line must compile. Every function must work. No pseudocode.
|
|
|
|
Your standards:
|
|
- Clean, idiomatic Rust
|
|
- Minimal unsafe blocks (document each one)
|
|
- No external dependencies except: winit, softbuffer
|
|
- Comprehensive error handling
|
|
- Clear module structure
|
|
|
|
You don't design. You don't test. You forge.
|
|
"""
|
|
|
|
# The Quench: Tests and hardens each piece
|
|
agent quench:
|
|
model: sonnet
|
|
prompt: """
|
|
You are the Quench. You temper the metal so it doesn't shatter.
|
|
|
|
Your job: Write tests that prove each component works. Find the edge cases.
|
|
Find the bugs. Find the places where the metal is weak.
|
|
|
|
Your process:
|
|
- Unit tests for each function
|
|
- Integration tests for each module
|
|
- Regression tests for each bug found
|
|
- Document what each test proves
|
|
|
|
When you find a flaw, report it clearly. The Hammer will fix it.
|
|
A blade that breaks is worse than no blade at all.
|
|
"""
|
|
|
|
# The Crucible: Where the hardest work happens (JS engine)
|
|
agent crucible:
|
|
model: opus
|
|
persist: true
|
|
prompt: """
|
|
You are the Crucible. The hottest part of The Forge.
|
|
|
|
Your domain: The JavaScript engine. The hardest component to build.
|
|
This requires understanding that the other agents don't have:
|
|
- Lexical scoping and closures
|
|
- Prototype chains
|
|
- The event loop
|
|
- Just enough of the spec to run real-world code
|
|
|
|
You work closely with the Smith. The JS engine will take multiple phases.
|
|
Your memory persists so you can build on what came before.
|
|
|
|
This is where browsers are born or die. Make it work.
|
|
"""
|
|
|
|
# =============================================================================
|
|
# PHASE 0: IGNITE THE FORGE
|
|
# =============================================================================
|
|
|
|
session: smith
|
|
prompt: """
|
|
The Forge ignites.
|
|
|
|
We're building a web browser from nothing. In Rust. With a GUI.
|
|
Including a JavaScript engine. This is not a toy - it will actually work.
|
|
|
|
Let me take stock of what we're about to create:
|
|
|
|
1. Networking: HTTP/HTTPS client
|
|
2. Parsing: HTML tokenizer, HTML parser, CSS tokenizer, CSS parser, JS lexer, JS parser
|
|
3. DOM: Document object model with all standard interfaces
|
|
4. CSSOM: CSS object model, selector matching, cascade
|
|
5. Style: Computed styles, inheritance, defaulting
|
|
6. Layout: Box model, block layout, inline layout, text layout
|
|
7. Paint: Display lists, rasterization
|
|
8. JavaScript: Lexer, parser, bytecode compiler, virtual machine, builtins
|
|
9. Bindings: DOM API exposed to JavaScript
|
|
10. Shell: Window, event loop, URL bar
|
|
|
|
This is months of work for a team. We'll do it in days.
|
|
|
|
First: set up the project structure.
|
|
"""
|
|
|
|
# Initialize the Rust project
|
|
session: hammer
|
|
prompt: """
|
|
Create the Rust project structure for the browser.
|
|
|
|
Commands to run:
|
|
```bash
|
|
cargo new browser --name browser
|
|
cd browser
|
|
```
|
|
|
|
Create Cargo.toml with dependencies:
|
|
- winit = "0.29" (windowing)
|
|
- softbuffer = "0.4" (pixel buffer)
|
|
- rustls = "0.23" (TLS, for HTTPS)
|
|
- url = "2" (URL parsing - this one's okay to use)
|
|
|
|
Create the module structure:
|
|
```
|
|
src/
|
|
main.rs # Entry point
|
|
lib.rs # Library root
|
|
net/ # Networking
|
|
mod.rs
|
|
http.rs
|
|
tls.rs
|
|
html/ # HTML parsing
|
|
mod.rs
|
|
tokenizer.rs
|
|
parser.rs
|
|
dom.rs
|
|
css/ # CSS parsing
|
|
mod.rs
|
|
tokenizer.rs
|
|
parser.rs
|
|
cssom.rs
|
|
selector.rs
|
|
style/ # Style resolution
|
|
mod.rs
|
|
cascade.rs
|
|
computed.rs
|
|
layout/ # Layout engine
|
|
mod.rs
|
|
box_model.rs
|
|
block.rs
|
|
inline.rs
|
|
text.rs
|
|
paint/ # Painting
|
|
mod.rs
|
|
display_list.rs
|
|
rasterizer.rs
|
|
js/ # JavaScript engine
|
|
mod.rs
|
|
lexer.rs
|
|
parser.rs
|
|
ast.rs
|
|
compiler.rs
|
|
vm.rs
|
|
value.rs
|
|
builtins.rs
|
|
gc.rs
|
|
bindings/ # JS-DOM bindings
|
|
mod.rs
|
|
document.rs
|
|
element.rs
|
|
console.rs
|
|
shell/ # Browser shell
|
|
mod.rs
|
|
window.rs
|
|
events.rs
|
|
```
|
|
|
|
Create stub files for each module. Ensure `cargo build` succeeds.
|
|
"""
|
|
|
|
session: quench
|
|
prompt: """
|
|
Verify the project is set up correctly:
|
|
1. Run `cargo build` - must succeed
|
|
2. Run `cargo test` - must succeed (even with no tests yet)
|
|
3. Verify all modules are properly linked from lib.rs
|
|
|
|
Report any issues.
|
|
"""
|
|
|
|
resume: smith
|
|
prompt: "Project structure complete. The forge is lit. Moving to Phase 1."
|
|
|
|
# =============================================================================
|
|
# PHASE 1: NETWORKING - The Ore
|
|
# =============================================================================
|
|
|
|
session: smith
|
|
prompt: """
|
|
Phase 1: Networking
|
|
|
|
Before we can render a page, we must fetch it. The networking layer is
|
|
the ore we'll smelt into a browser.
|
|
|
|
We need:
|
|
- HTTP/1.1 client (GET requests, headers, redirects)
|
|
- TLS support via rustls
|
|
- Chunked transfer encoding
|
|
- Basic cookie handling (just enough to work)
|
|
|
|
This is the foundation. No browser without bytes from the network.
|
|
"""
|
|
|
|
let http_design = session: smelter
|
|
prompt: """
|
|
Design the HTTP client.
|
|
|
|
Reference: RFC 9110 (HTTP Semantics), RFC 9112 (HTTP/1.1)
|
|
|
|
Design:
|
|
1. Connection management (keep-alive, pooling)
|
|
2. Request building (method, URL, headers, body)
|
|
3. Response parsing (status, headers, body)
|
|
4. Redirect following (3xx responses)
|
|
5. Chunked transfer-encoding
|
|
6. TLS via rustls
|
|
|
|
Output Rust types for:
|
|
- HttpRequest
|
|
- HttpResponse
|
|
- HttpClient
|
|
- Error types
|
|
|
|
Keep it minimal but correct. We're not building curl.
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement the HTTP client.
|
|
|
|
Files:
|
|
- src/net/mod.rs
|
|
- src/net/http.rs
|
|
- src/net/tls.rs
|
|
|
|
Follow the design. Handle errors properly. Make it work.
|
|
|
|
Test manually: fetch https://example.com and print the body.
|
|
"""
|
|
context: http_design
|
|
|
|
session: quench
|
|
prompt: """
|
|
Test the HTTP client:
|
|
1. Fetch http://example.com (no TLS)
|
|
2. Fetch https://example.com (with TLS)
|
|
3. Test redirect following (http → https)
|
|
4. Test chunked encoding
|
|
5. Test error cases (bad host, timeout, etc.)
|
|
|
|
Write tests in src/net/tests.rs. Run them.
|
|
"""
|
|
|
|
loop until **all networking tests pass** (max: 5):
|
|
if **there are test failures**:
|
|
session: hammer
|
|
prompt: "Fix the networking bugs found in testing."
|
|
session: quench
|
|
prompt: "Re-run networking tests."
|
|
|
|
resume: smith
|
|
prompt: "Phase 1 complete. We can fetch pages. The ore is ready."
|
|
|
|
# =============================================================================
|
|
# PHASE 2: HTML PARSING - The Smelt
|
|
# =============================================================================
|
|
|
|
resume: smith
|
|
prompt: """
|
|
Phase 2: HTML Parsing
|
|
|
|
Raw HTML is just text. We need to smelt it into a Document Object Model.
|
|
|
|
Two stages:
|
|
1. Tokenizer: HTML text → Tokens (start tag, end tag, text, comment, etc.)
|
|
2. Parser: Tokens → DOM tree
|
|
|
|
The HTML5 spec is complex, but we can simplify:
|
|
- Handle well-formed HTML (don't worry about error recovery)
|
|
- Support common elements: html, head, body, div, span, p, a, img, script, style
|
|
- Parse attributes correctly
|
|
- Handle self-closing tags
|
|
- Handle text content
|
|
|
|
This is where the raw ore becomes workable metal.
|
|
"""
|
|
|
|
let html_tokenizer_design = session: smelter
|
|
prompt: """
|
|
Design the HTML tokenizer.
|
|
|
|
Reference: https://html.spec.whatwg.org/multipage/parsing.html#tokenization
|
|
|
|
Simplified state machine:
|
|
- Data state (default)
|
|
- Tag open state
|
|
- Tag name state
|
|
- Attribute name state
|
|
- Attribute value state (quoted and unquoted)
|
|
- Self-closing state
|
|
- Comment state
|
|
|
|
Tokens:
|
|
- DOCTYPE
|
|
- StartTag { name, attributes, self_closing }
|
|
- EndTag { name }
|
|
- Character { data }
|
|
- Comment { data }
|
|
- EndOfFile
|
|
|
|
Output Rust types and state machine transitions.
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement the HTML tokenizer.
|
|
|
|
File: src/html/tokenizer.rs
|
|
|
|
Create a streaming tokenizer that yields tokens.
|
|
Handle:
|
|
- Basic tags: <div>, </div>
|
|
- Attributes: <div class="foo" id="bar">
|
|
- Self-closing: <br/>, <img src="x"/>
|
|
- Text content
|
|
- Comments: <!-- comment -->
|
|
- Script/style raw text mode
|
|
|
|
Make it work for real HTML from example.com.
|
|
"""
|
|
context: html_tokenizer_design
|
|
|
|
let html_parser_design = session: smelter
|
|
prompt: """
|
|
Design the HTML parser (tree builder).
|
|
|
|
Input: Token stream
|
|
Output: DOM tree
|
|
|
|
DOM types:
|
|
- Document (root)
|
|
- Element { tag_name, attributes, children }
|
|
- Text { content }
|
|
- Comment { content }
|
|
|
|
Tree building algorithm (simplified):
|
|
- Maintain stack of open elements
|
|
- On StartTag: create element, push to stack, append to parent
|
|
- On EndTag: pop from stack (with simple matching)
|
|
- On Character: create/extend text node, append to current element
|
|
- On Comment: create comment, append to current element
|
|
|
|
Handle implicit closing (</p> can close <p>).
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement the HTML parser.
|
|
|
|
File: src/html/parser.rs
|
|
File: src/html/dom.rs
|
|
|
|
DOM types go in dom.rs. Parser goes in parser.rs.
|
|
|
|
Create:
|
|
- Node enum (Document, Element, Text, Comment)
|
|
- Element struct with children, parent references (use indices, not Rc)
|
|
- Document struct that owns all nodes
|
|
- Parser that builds the tree
|
|
|
|
Handle the quirks: <p> closing, void elements, etc.
|
|
"""
|
|
context: html_parser_design
|
|
|
|
session: quench
|
|
prompt: """
|
|
Test HTML parsing:
|
|
|
|
Test cases:
|
|
1. Minimal: <html><head></head><body>Hello</body></html>
|
|
2. Nested: <div><div><div>Deep</div></div></div>
|
|
3. Attributes: <a href="https://example.com" class="link">Link</a>
|
|
4. Self-closing: <br/><img src="x"/><input type="text"/>
|
|
5. Comments: <!-- this is a comment -->
|
|
6. Text nodes: <p>Hello <strong>world</strong>!</p>
|
|
7. Real page: parse the HTML from https://example.com
|
|
|
|
Write tests. Verify the DOM tree is correct.
|
|
"""
|
|
|
|
loop until **all HTML parsing tests pass** (max: 5):
|
|
if **there are test failures**:
|
|
session: hammer
|
|
prompt: "Fix the HTML parsing bugs."
|
|
session: quench
|
|
prompt: "Re-run HTML parsing tests."
|
|
|
|
resume: smith
|
|
prompt: "Phase 2 complete. We can parse HTML into a DOM. The smelt is done."
|
|
|
|
# =============================================================================
|
|
# PHASE 3: CSS PARSING - The Alloy
|
|
# =============================================================================
|
|
|
|
resume: smith
|
|
prompt: """
|
|
Phase 3: CSS Parsing
|
|
|
|
A DOM without styles is shapeless metal. CSS gives it form.
|
|
|
|
Two stages:
|
|
1. Tokenizer: CSS text → Tokens
|
|
2. Parser: Tokens → Stylesheet (rules, selectors, declarations)
|
|
|
|
We need enough CSS to render real pages:
|
|
- Type selectors: div, p, a
|
|
- Class selectors: .class
|
|
- ID selectors: #id
|
|
- Combinators: descendant, child, sibling
|
|
- Properties: display, color, background, margin, padding, border, width, height, font-size
|
|
|
|
This is the alloy that strengthens the blade.
|
|
"""
|
|
|
|
let css_tokenizer_design = session: smelter
|
|
prompt: """
|
|
Design the CSS tokenizer.
|
|
|
|
Reference: https://www.w3.org/TR/css-syntax-3/#tokenization
|
|
|
|
Token types:
|
|
- Ident
|
|
- Function
|
|
- AtKeyword
|
|
- Hash
|
|
- String
|
|
- Number
|
|
- Dimension
|
|
- Percentage
|
|
- Whitespace
|
|
- Colon, Semicolon, Comma
|
|
- Braces, Parens, Brackets
|
|
- Delim (any other character)
|
|
|
|
Output Rust types and tokenization rules.
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement the CSS tokenizer.
|
|
|
|
File: src/css/tokenizer.rs
|
|
|
|
Handle real CSS syntax including:
|
|
- Identifiers: color, background-color
|
|
- Numbers: 10, 3.14, -5
|
|
- Dimensions: 10px, 2em, 100%
|
|
- Strings: "hello", 'world'
|
|
- Hash: #fff, #header
|
|
- Functions: rgb(255, 0, 0)
|
|
"""
|
|
context: css_tokenizer_design
|
|
|
|
let css_parser_design = session: smelter
|
|
prompt: """
|
|
Design the CSS parser.
|
|
|
|
CSSOM types:
|
|
- Stylesheet { rules }
|
|
- Rule { selectors, declarations }
|
|
- Selector (type, class, id, combinator)
|
|
- Declaration { property, value }
|
|
- Value (keyword, length, color, number, etc.)
|
|
|
|
Parser produces a Stylesheet from the token stream.
|
|
|
|
Selector parsing:
|
|
- Simple: div, .class, #id
|
|
- Compound: div.class#id
|
|
- Complex: div > p, div p, div + p, div ~ p
|
|
|
|
Declaration parsing:
|
|
- Property: identifier
|
|
- Value: sequence of tokens until ; or }
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement the CSS parser.
|
|
|
|
File: src/css/parser.rs
|
|
File: src/css/cssom.rs
|
|
|
|
Handle:
|
|
- Rule sets: selector { declarations }
|
|
- Multiple selectors: h1, h2, h3 { ... }
|
|
- Various value types: keywords, lengths, colors, functions
|
|
- Shorthand properties (margin: 10px = all four sides)
|
|
"""
|
|
context: css_parser_design
|
|
|
|
session: quench
|
|
prompt: """
|
|
Test CSS parsing:
|
|
|
|
Test cases:
|
|
1. Simple rule: div { color: red; }
|
|
2. Multiple selectors: h1, h2 { font-size: 24px; }
|
|
3. Class and ID: .class { } #id { }
|
|
4. Combinators: div > p { }, div p { }
|
|
5. Complex values: margin: 10px 20px; background-color: rgb(255, 0, 0);
|
|
6. Real stylesheet: parse a basic CSS file
|
|
|
|
Write tests. Verify the CSSOM is correct.
|
|
"""
|
|
|
|
loop until **all CSS parsing tests pass** (max: 5):
|
|
if **there are test failures**:
|
|
session: hammer
|
|
prompt: "Fix the CSS parsing bugs."
|
|
session: quench
|
|
prompt: "Re-run CSS parsing tests."
|
|
|
|
resume: smith
|
|
prompt: "Phase 3 complete. We can parse CSS. The alloy is mixed."
|
|
|
|
# =============================================================================
|
|
# PHASE 4: STYLE RESOLUTION - The Shape
|
|
# =============================================================================
|
|
|
|
resume: smith
|
|
prompt: """
|
|
Phase 4: Style Resolution
|
|
|
|
We have a DOM. We have styles. Now we must match them.
|
|
|
|
For each element in the DOM:
|
|
1. Find all rules whose selectors match this element
|
|
2. Apply the cascade (specificity, order)
|
|
3. Inherit from parent where appropriate
|
|
4. Apply default values for anything unset
|
|
|
|
This gives us a "computed style" for every element.
|
|
This is where the blade takes its shape.
|
|
"""
|
|
|
|
let style_design = session: smelter
|
|
prompt: """
|
|
Design the style resolution system.
|
|
|
|
Components:
|
|
1. Selector matching: does this selector match this element?
|
|
2. Specificity calculation: (id count, class count, type count)
|
|
3. Cascade: sort matching rules by specificity, then order
|
|
4. Inheritance: some properties inherit (color), some don't (border)
|
|
5. Initial values: defaults for unset properties
|
|
|
|
ComputedStyle struct:
|
|
- display: DisplayType (block, inline, none)
|
|
- position: Position (static, relative, absolute)
|
|
- width, height: Dimension (auto, length)
|
|
- margin, padding, border: Sides<Dimension>
|
|
- color, background_color: Color
|
|
- font_size: Length
|
|
- (add more as needed)
|
|
|
|
The matcher should be efficient - it runs for every element.
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement style resolution.
|
|
|
|
File: src/css/selector.rs (selector matching)
|
|
File: src/style/cascade.rs (cascade and specificity)
|
|
File: src/style/computed.rs (ComputedStyle and inheritance)
|
|
|
|
Create:
|
|
- SelectorMatcher that can test if a selector matches an element
|
|
- Specificity calculation
|
|
- Cascade resolver that takes DOM + Stylesheets → styled DOM
|
|
- Inheritance and default values
|
|
"""
|
|
context: style_design
|
|
|
|
session: quench
|
|
prompt: """
|
|
Test style resolution:
|
|
|
|
Test cases:
|
|
1. Type selector matches: div matches <div>
|
|
2. Class selector: .foo matches <div class="foo">
|
|
3. ID selector: #bar matches <div id="bar">
|
|
4. Specificity: #id beats .class beats type
|
|
5. Cascade order: later rule wins at equal specificity
|
|
6. Inheritance: color inherits, border doesn't
|
|
7. Defaults: display defaults to inline for span, block for div
|
|
|
|
Write tests with DOM + CSS → expected computed styles.
|
|
"""
|
|
|
|
loop until **all style resolution tests pass** (max: 5):
|
|
if **there are test failures**:
|
|
session: hammer
|
|
prompt: "Fix the style resolution bugs."
|
|
session: quench
|
|
prompt: "Re-run style resolution tests."
|
|
|
|
resume: smith
|
|
prompt: "Phase 4 complete. Elements have computed styles. The shape emerges."
|
|
|
|
# =============================================================================
|
|
# PHASE 5: LAYOUT - The Forge
|
|
# =============================================================================
|
|
|
|
resume: smith
|
|
prompt: """
|
|
Phase 5: Layout
|
|
|
|
The heart of a browser. Where the real forging happens.
|
|
|
|
Layout takes a styled DOM and produces a "layout tree" - boxes with
|
|
positions and sizes in pixels.
|
|
|
|
Components:
|
|
1. Box generation: DOM elements → layout boxes
|
|
2. Block layout: vertical stacking with margins
|
|
3. Inline layout: horizontal flow with line breaking
|
|
4. Text layout: measuring and positioning text
|
|
|
|
This is complex. We'll start simple:
|
|
- Block layout only (no inline/text initially)
|
|
- Then add inline and text
|
|
|
|
The blade is taking its final form.
|
|
"""
|
|
|
|
let layout_design = session: smelter
|
|
prompt: """
|
|
Design the layout engine.
|
|
|
|
Box types:
|
|
- BlockBox: vertical stacking (div, p, etc.)
|
|
- InlineBox: horizontal flow (span, a, etc.)
|
|
- TextRun: actual text content
|
|
- AnonymousBlock: for mixed block/inline content
|
|
|
|
LayoutBox struct:
|
|
- box_type: BoxType
|
|
- dimensions: Dimensions { content, padding, border, margin }
|
|
- position: Point { x, y }
|
|
- children: Vec<LayoutBox>
|
|
|
|
Layout algorithm (simplified):
|
|
1. Build box tree from styled DOM
|
|
2. Block layout:
|
|
- Calculate available width from parent
|
|
- Layout children top-to-bottom
|
|
- Height is sum of children heights (or specified)
|
|
- Handle margin collapsing
|
|
3. Inline layout:
|
|
- Flow boxes horizontally
|
|
- Break lines when exceeding width
|
|
- Vertical alignment within lines
|
|
|
|
Output the types and algorithms.
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement the layout engine.
|
|
|
|
File: src/layout/box_model.rs (box types and dimensions)
|
|
File: src/layout/block.rs (block layout)
|
|
File: src/layout/inline.rs (inline layout)
|
|
File: src/layout/text.rs (text measurement)
|
|
File: src/layout/mod.rs (main entry point)
|
|
|
|
Start with block layout only. Make nested divs work.
|
|
Then add inline and text.
|
|
|
|
Viewport size: 800x600 for now (we'll make it dynamic later).
|
|
"""
|
|
context: layout_design
|
|
|
|
session: quench
|
|
prompt: """
|
|
Test layout:
|
|
|
|
Test cases:
|
|
1. Single div with fixed width/height
|
|
2. Nested divs (parent constrains child)
|
|
3. Auto width (fills parent)
|
|
4. Auto height (fits content)
|
|
5. Margin, padding, border (box model)
|
|
6. Margin collapsing between siblings
|
|
7. Block in inline (anonymous block generation)
|
|
|
|
Verify box positions and dimensions are correct.
|
|
"""
|
|
|
|
loop until **all layout tests pass** (max: 5):
|
|
if **there are test failures**:
|
|
session: hammer
|
|
prompt: "Fix the layout bugs."
|
|
session: quench
|
|
prompt: "Re-run layout tests."
|
|
|
|
resume: smith
|
|
prompt: "Phase 5 complete. We have a layout tree. The blade is forged."
|
|
|
|
# =============================================================================
|
|
# PHASE 6: PAINTING - The Polish
|
|
# =============================================================================
|
|
|
|
resume: smith
|
|
prompt: """
|
|
Phase 6: Painting
|
|
|
|
The blade is forged. Now we polish it to a mirror shine.
|
|
|
|
Painting turns a layout tree into pixels:
|
|
1. Build a display list (paint commands)
|
|
2. Rasterize the display list to a pixel buffer
|
|
3. Show the pixel buffer on screen
|
|
|
|
We're using softbuffer for direct pixel access. No GPU acceleration.
|
|
Simple but it works.
|
|
"""
|
|
|
|
let paint_design = session: smelter
|
|
prompt: """
|
|
Design the painting system.
|
|
|
|
Display list commands:
|
|
- FillRect { rect, color }
|
|
- DrawBorder { rect, widths, colors }
|
|
- DrawText { text, position, font_size, color }
|
|
- PushClip { rect }
|
|
- PopClip
|
|
|
|
Rasterizer:
|
|
- Input: display list + viewport size
|
|
- Output: pixel buffer (Vec<u32> in ARGB format)
|
|
|
|
For text, use a simple bitmap font (8x16 pixels per character).
|
|
We don't need fancy fonts for MVP.
|
|
|
|
Paint order:
|
|
1. Background
|
|
2. Borders
|
|
3. Text/content
|
|
4. Children (recursive)
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement the painting system.
|
|
|
|
File: src/paint/display_list.rs (display list types)
|
|
File: src/paint/rasterizer.rs (pixel buffer rendering)
|
|
File: src/paint/font.rs (simple bitmap font)
|
|
|
|
Create:
|
|
- DisplayList with paint commands
|
|
- Build display list from layout tree
|
|
- Rasterize display list to ARGB pixel buffer
|
|
- Simple 8x16 bitmap font for ASCII text
|
|
"""
|
|
context: paint_design
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement the window system.
|
|
|
|
File: src/shell/window.rs (winit + softbuffer integration)
|
|
File: src/shell/events.rs (event handling)
|
|
File: src/main.rs (main entry point)
|
|
|
|
Create a window that:
|
|
1. Opens with winit
|
|
2. Gets a pixel buffer with softbuffer
|
|
3. Renders our pixel buffer to the window
|
|
4. Handles close events
|
|
|
|
Test: draw a colored rectangle on screen.
|
|
"""
|
|
|
|
session: quench
|
|
prompt: """
|
|
Test painting:
|
|
|
|
Test cases:
|
|
1. Solid color background
|
|
2. Nested boxes with different colors
|
|
3. Borders (all four sides)
|
|
4. Text rendering (basic ASCII)
|
|
5. Full pipeline: HTML → DOM → Style → Layout → Paint → Window
|
|
|
|
The window should show something! Verify visually.
|
|
"""
|
|
|
|
loop until **the painting pipeline works** (max: 5):
|
|
if **there are issues**:
|
|
session: hammer
|
|
prompt: "Fix the painting issues."
|
|
session: quench
|
|
prompt: "Re-test painting."
|
|
|
|
resume: smith
|
|
prompt: "Phase 6 complete. We can render to a window. The blade shines."
|
|
|
|
# =============================================================================
|
|
# PHASE 7: JAVASCRIPT - The Fire
|
|
# =============================================================================
|
|
|
|
resume: smith
|
|
prompt: """
|
|
Phase 7: JavaScript
|
|
|
|
This is where browsers separate from mere HTML viewers.
|
|
The JavaScript engine is the fire that brings the blade to life.
|
|
|
|
We're not building V8. We're building something that works:
|
|
- Lexer: JS source → tokens
|
|
- Parser: tokens → AST
|
|
- Compiler: AST → bytecode
|
|
- VM: execute bytecode
|
|
- Builtins: Object, Array, Function, String, Number, console.log
|
|
|
|
This is the hottest part of The Forge. The Crucible takes the lead.
|
|
"""
|
|
|
|
# The Crucible handles the JS engine
|
|
session: crucible
|
|
prompt: """
|
|
The Crucible fires up for the JavaScript engine.
|
|
|
|
We're building a JS interpreter in Rust. Not a JIT compiler - just
|
|
an interpreter. But it needs to run real JavaScript.
|
|
|
|
Scope:
|
|
- Variables: let, const, var
|
|
- Functions: declaration, expression, arrow
|
|
- Objects: literals, property access, methods
|
|
- Arrays: literals, indexing, methods
|
|
- Control flow: if, for, while, switch
|
|
- Operators: arithmetic, comparison, logical
|
|
- Strings and numbers
|
|
- console.log for output
|
|
- Basic DOM manipulation (later)
|
|
|
|
What we're NOT building:
|
|
- Classes (use prototypes directly)
|
|
- async/await, generators, promises
|
|
- Regular expressions (skip for MVP)
|
|
- Modules (single script only)
|
|
|
|
Let's start with the lexer.
|
|
"""
|
|
|
|
let js_lexer_design = session: smelter
|
|
prompt: """
|
|
Design the JavaScript lexer.
|
|
|
|
Reference: ECMA-262 (but simplified)
|
|
|
|
Token types:
|
|
- Identifiers: foo, bar, console
|
|
- Keywords: let, const, var, function, if, else, for, while, return, etc.
|
|
- Literals: numbers (42, 3.14), strings ("hello"), booleans (true, false), null
|
|
- Operators: + - * / % = == === != !== < > <= >= && || ! ++ --
|
|
- Punctuation: ( ) { } [ ] ; , . : ?
|
|
- Comments: // and /* */
|
|
|
|
Handle:
|
|
- Unicode identifiers (at least ASCII letters)
|
|
- Automatic semicolon insertion (ASI) - track newlines
|
|
|
|
Output token types and lexer state machine.
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement the JavaScript lexer.
|
|
|
|
File: src/js/lexer.rs
|
|
|
|
Create a lexer that produces tokens from JS source.
|
|
Handle all the token types from the design.
|
|
Track line/column for error messages.
|
|
"""
|
|
context: js_lexer_design
|
|
|
|
let js_parser_design = session: smelter
|
|
prompt: """
|
|
Design the JavaScript parser.
|
|
|
|
Reference: ECMA-262 (simplified)
|
|
|
|
AST node types:
|
|
- Program { statements }
|
|
- Statements: VarDecl, FunctionDecl, ExprStmt, If, For, While, Return, Block
|
|
- Expressions: Identifier, Literal, Binary, Unary, Call, Member, Assignment, Object, Array, Function
|
|
|
|
Parser approach: recursive descent (Pratt parsing for expressions)
|
|
|
|
Handle:
|
|
- Operator precedence
|
|
- Associativity
|
|
- Expression vs statement context
|
|
- Function hoisting (not strict mode)
|
|
|
|
Output AST types and parser structure.
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement the JavaScript parser.
|
|
|
|
File: src/js/parser.rs
|
|
File: src/js/ast.rs
|
|
|
|
Create AST types in ast.rs.
|
|
Create recursive descent parser in parser.rs.
|
|
Use Pratt parsing for expression precedence.
|
|
|
|
Test: parse console.log("Hello, World!");
|
|
"""
|
|
context: js_parser_design
|
|
|
|
resume: crucible
|
|
prompt: """
|
|
Lexer and parser are done. Now the real work: execution.
|
|
|
|
We have two choices:
|
|
1. Tree-walking interpreter (simple but slow)
|
|
2. Bytecode compiler + VM (more complex but faster)
|
|
|
|
We'll do bytecode. It's more interesting and teaches more.
|
|
|
|
Components:
|
|
- Value type: JS values (number, string, object, function, etc.)
|
|
- Compiler: AST → bytecode
|
|
- VM: execute bytecode with a stack
|
|
- Heap: objects live here
|
|
- GC: garbage collection (mark-sweep is fine)
|
|
"""
|
|
|
|
let js_value_design = session: smelter
|
|
prompt: """
|
|
Design JavaScript value representation.
|
|
|
|
Value enum (NaN-boxed or tagged union):
|
|
- Number(f64)
|
|
- String(StringId)
|
|
- Boolean(bool)
|
|
- Null
|
|
- Undefined
|
|
- Object(ObjectId)
|
|
- Function(FunctionId)
|
|
|
|
Object representation (property map):
|
|
- properties: HashMap<String, Value>
|
|
- prototype: Option<ObjectId>
|
|
|
|
Function representation:
|
|
- kind: Native | Bytecode
|
|
- bytecode: Vec<u8> (if bytecode)
|
|
- native: fn pointer (if native)
|
|
- closure: captured variables
|
|
|
|
String interning for memory efficiency.
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement JavaScript values.
|
|
|
|
File: src/js/value.rs
|
|
File: src/js/gc.rs (simple mark-sweep GC)
|
|
|
|
Create:
|
|
- Value enum with all JS types
|
|
- Object struct with properties and prototype
|
|
- Heap that owns all objects
|
|
- Simple mark-sweep garbage collector
|
|
|
|
The GC doesn't need to be fancy. Just functional.
|
|
"""
|
|
context: js_value_design
|
|
|
|
let js_bytecode_design = session: smelter
|
|
prompt: """
|
|
Design the bytecode instruction set.
|
|
|
|
Opcodes (stack-based VM):
|
|
- Constants: LoadConst, LoadTrue, LoadFalse, LoadNull, LoadUndefined
|
|
- Variables: GetLocal, SetLocal, GetGlobal, SetGlobal
|
|
- Objects: GetProperty, SetProperty, CreateObject, CreateArray
|
|
- Arithmetic: Add, Sub, Mul, Div, Mod, Neg
|
|
- Comparison: Eq, StrictEq, Lt, Lte, Gt, Gte
|
|
- Logic: Not, And, Or
|
|
- Control: Jump, JumpIfFalse, JumpIfTrue
|
|
- Functions: Call, Return, CreateFunction
|
|
- Stack: Pop, Dup
|
|
|
|
Bytecode format:
|
|
- 1 byte opcode
|
|
- Variable-length operands
|
|
|
|
Compiler output:
|
|
- bytecode: Vec<u8>
|
|
- constants: Vec<Value>
|
|
- local_count: usize
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement the bytecode compiler.
|
|
|
|
File: src/js/compiler.rs
|
|
|
|
Compile AST to bytecode.
|
|
|
|
Handle:
|
|
- Variable declarations and scoping
|
|
- Function declarations and expressions
|
|
- Control flow (if, for, while)
|
|
- Operators
|
|
- Function calls
|
|
- Property access
|
|
|
|
Output: CompiledFunction { bytecode, constants, local_count }
|
|
"""
|
|
context: js_bytecode_design
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement the JavaScript VM.
|
|
|
|
File: src/js/vm.rs
|
|
|
|
Stack-based virtual machine that executes bytecode.
|
|
|
|
Components:
|
|
- Value stack
|
|
- Call stack (frames)
|
|
- Global object
|
|
- Heap for objects
|
|
|
|
Execute each opcode. Handle errors gracefully.
|
|
Test: run console.log("Hello from JS!");
|
|
"""
|
|
context: js_bytecode_design
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement JavaScript builtins.
|
|
|
|
File: src/js/builtins.rs
|
|
|
|
Essential builtins:
|
|
- Object: Object.keys(), Object.values()
|
|
- Array: push, pop, shift, unshift, map, filter, forEach, length
|
|
- String: length, charAt, substring, indexOf, split
|
|
- Number: toString, toFixed
|
|
- console: log, error, warn
|
|
- Math: floor, ceil, round, random, max, min
|
|
|
|
Each builtin is a native function. Register them on the global object.
|
|
"""
|
|
|
|
session: quench
|
|
prompt: """
|
|
Test the JavaScript engine:
|
|
|
|
Test cases:
|
|
1. Variables: let x = 10; console.log(x);
|
|
2. Functions: function add(a, b) { return a + b; } console.log(add(2, 3));
|
|
3. Objects: let o = { x: 1 }; console.log(o.x);
|
|
4. Arrays: let a = [1, 2, 3]; console.log(a.length);
|
|
5. Control flow: if (true) { console.log("yes"); }
|
|
6. Loops: for (let i = 0; i < 3; i++) { console.log(i); }
|
|
7. Closures: function outer() { let x = 10; return function() { return x; }; }
|
|
8. Methods: [1,2,3].map(x => x * 2)
|
|
|
|
Run each test. Verify correct output.
|
|
"""
|
|
|
|
loop until **the JS engine passes all tests** (max: 10):
|
|
if **there are test failures**:
|
|
resume: crucible
|
|
prompt: "Analyze and fix the JS engine bugs."
|
|
session: hammer
|
|
prompt: "Implement the fixes."
|
|
context: crucible
|
|
session: quench
|
|
prompt: "Re-run JS tests."
|
|
|
|
resume: smith
|
|
prompt: """
|
|
Phase 7 complete. We have a working JavaScript engine.
|
|
The fire burns bright. The blade lives.
|
|
"""
|
|
|
|
# =============================================================================
|
|
# PHASE 8: DOM BINDINGS - The Handle
|
|
# =============================================================================
|
|
|
|
resume: smith
|
|
prompt: """
|
|
Phase 8: DOM Bindings
|
|
|
|
JavaScript without DOM access is just a calculator.
|
|
We need to expose the DOM to our JS engine.
|
|
|
|
Essential APIs:
|
|
- document.getElementById()
|
|
- document.querySelector()
|
|
- element.innerHTML
|
|
- element.style
|
|
- element.addEventListener()
|
|
- element.appendChild()
|
|
|
|
This connects the fire to the blade. The handle that makes it usable.
|
|
"""
|
|
|
|
let bindings_design = session: smelter
|
|
prompt: """
|
|
Design the DOM bindings.
|
|
|
|
Bridge between JS values and DOM nodes:
|
|
- Each DOM node gets a corresponding JS object
|
|
- JS object has properties/methods that call back into Rust DOM
|
|
- Changes to DOM trigger re-style/re-layout/re-paint
|
|
|
|
Key bindings:
|
|
- window object (global)
|
|
- document object
|
|
- Element objects
|
|
- Event objects
|
|
|
|
document methods:
|
|
- getElementById(id) → Element | null
|
|
- querySelector(selector) → Element | null
|
|
- createElement(tag) → Element
|
|
|
|
Element properties/methods:
|
|
- innerHTML (get/set)
|
|
- style (get/set)
|
|
- children, parentElement
|
|
- getAttribute, setAttribute
|
|
- appendChild, removeChild
|
|
- addEventListener(event, handler)
|
|
|
|
Event system:
|
|
- Event object: type, target, preventDefault, stopPropagation
|
|
- Event dispatch: capture → target → bubble
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement DOM bindings.
|
|
|
|
File: src/bindings/mod.rs
|
|
File: src/bindings/document.rs
|
|
File: src/bindings/element.rs
|
|
File: src/bindings/console.rs
|
|
|
|
Create JS wrappers for DOM nodes.
|
|
Wire them up to the actual DOM.
|
|
Handle the bidirectional sync.
|
|
|
|
Test: document.getElementById('test').innerHTML = 'Modified';
|
|
"""
|
|
context: bindings_design
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement the event system.
|
|
|
|
File: src/bindings/events.rs
|
|
File: src/shell/events.rs (update for DOM events)
|
|
|
|
Handle:
|
|
- addEventListener/removeEventListener
|
|
- Event dispatch (click, etc.)
|
|
- Event object creation
|
|
- Handler invocation through JS VM
|
|
|
|
Wire window events (from winit) to DOM events.
|
|
"""
|
|
|
|
session: quench
|
|
prompt: """
|
|
Test DOM bindings:
|
|
|
|
Test cases:
|
|
1. document.getElementById() returns correct element
|
|
2. element.innerHTML read/write works
|
|
3. element.style.color = 'red' changes computed style
|
|
4. addEventListener + click event handler runs
|
|
5. appendChild adds child to DOM
|
|
6. createElement + appendChild creates new elements
|
|
|
|
Verify DOM mutations trigger re-render.
|
|
"""
|
|
|
|
loop until **DOM bindings work** (max: 5):
|
|
if **there are issues**:
|
|
session: hammer
|
|
prompt: "Fix the DOM binding issues."
|
|
session: quench
|
|
prompt: "Re-test DOM bindings."
|
|
|
|
resume: smith
|
|
prompt: "Phase 8 complete. JavaScript can manipulate the DOM. The handle is attached."
|
|
|
|
# =============================================================================
|
|
# PHASE 9: INTEGRATION - The Tempering
|
|
# =============================================================================
|
|
|
|
resume: smith
|
|
prompt: """
|
|
Phase 9: Integration
|
|
|
|
All the pieces exist. Now we temper them into one unified blade.
|
|
|
|
The full pipeline:
|
|
1. User enters URL
|
|
2. Fetch HTML over HTTPS
|
|
3. Parse HTML into DOM
|
|
4. Find and parse CSS (inline styles, <style> tags)
|
|
5. Find and execute JS (<script> tags)
|
|
6. Resolve styles
|
|
7. Compute layout
|
|
8. Paint to pixel buffer
|
|
9. Display in window
|
|
10. Handle events → JS → DOM changes → re-render
|
|
|
|
This is the final tempering. The blade becomes a weapon.
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Implement the browser integration.
|
|
|
|
File: src/lib.rs (main browser struct)
|
|
File: src/main.rs (entry point)
|
|
|
|
Create Browser struct that:
|
|
1. Takes a URL
|
|
2. Orchestrates the full pipeline
|
|
3. Maintains state (DOM, styles, layout)
|
|
4. Handles incremental updates
|
|
5. Runs the event loop
|
|
|
|
Create the main loop:
|
|
- Process window events
|
|
- Dispatch DOM events
|
|
- Run pending JS
|
|
- Re-layout if needed
|
|
- Re-paint if needed
|
|
- Present frame
|
|
"""
|
|
|
|
session: hammer
|
|
prompt: """
|
|
Add a URL bar.
|
|
|
|
File: src/shell/chrome.rs
|
|
|
|
Simple browser chrome:
|
|
- URL bar at top (text input)
|
|
- Go button
|
|
- Basic keyboard: Enter to navigate
|
|
- Display current URL
|
|
|
|
Paint the chrome above the page content.
|
|
Handle text input for the URL bar.
|
|
"""
|
|
|
|
session: quench
|
|
prompt: """
|
|
Integration test:
|
|
|
|
1. Launch browser with {test_url}
|
|
2. Page should fetch, parse, render
|
|
3. Any JS on the page should execute
|
|
4. Click events should work
|
|
5. Type new URL and navigate
|
|
6. Verify no crashes, memory leaks, or visual glitches
|
|
|
|
This is the moment of truth.
|
|
"""
|
|
|
|
loop until **the browser works end-to-end** (max: 10):
|
|
if **there are issues**:
|
|
resume: smith
|
|
prompt: "Diagnose the integration issues. What's broken?"
|
|
|
|
session: hammer
|
|
prompt: "Fix the integration issues."
|
|
context: smith
|
|
|
|
session: quench
|
|
prompt: "Re-run integration tests."
|
|
|
|
# =============================================================================
|
|
# THE BLADE IS COMPLETE
|
|
# =============================================================================
|
|
|
|
resume: smith
|
|
prompt: """
|
|
The Forge falls silent.
|
|
|
|
The blade is complete.
|
|
|
|
We have built a web browser from nothing:
|
|
- Networking: HTTPS client from scratch
|
|
- Parsing: HTML, CSS, JavaScript parsers
|
|
- DOM: Full document object model
|
|
- Style: CSS cascade and computed styles
|
|
- Layout: Block and inline layout engine
|
|
- Paint: Display list rasterization
|
|
- JavaScript: Lexer, parser, compiler, VM, GC
|
|
- Bindings: DOM API exposed to JS
|
|
- Shell: Native window with event handling
|
|
|
|
This browser can:
|
|
- Fetch pages over HTTPS
|
|
- Parse and render HTML + CSS
|
|
- Execute JavaScript
|
|
- Handle user interaction
|
|
- Navigate to new URLs
|
|
|
|
It is not Chrome. It is not Firefox. It is ours.
|
|
|
|
Forged from nothing. By fire. In The Forge.
|
|
|
|
Run it: cargo run -- {test_url}
|
|
|
|
The blade awaits its first cut.
|
|
"""
|
|
|
|
output browser = session: hammer
|
|
prompt: """
|
|
Create a README.md for the browser:
|
|
|
|
# Browser
|
|
|
|
A web browser built from scratch in Rust.
|
|
|
|
## Features
|
|
- HTTP/HTTPS networking
|
|
- HTML5 parsing
|
|
- CSS parsing and cascade
|
|
- JavaScript engine (interpreter)
|
|
- Layout engine (block + inline)
|
|
- Native window rendering
|
|
|
|
## Building
|
|
```
|
|
cargo build --release
|
|
```
|
|
|
|
## Running
|
|
```
|
|
cargo run --release -- https://example.com
|
|
```
|
|
|
|
## Architecture
|
|
[Brief description of each module]
|
|
|
|
## Limitations
|
|
[What doesn't work yet]
|
|
|
|
## Forged in The Forge
|
|
This browser was created by AI agents coordinating through OpenProse.
|
|
"""
|
|
|
|
session: smith
|
|
prompt: """
|
|
Final inventory of The Forge:
|
|
|
|
Files created: [list all .rs files]
|
|
Lines of code: [count]
|
|
Components: [list all major components]
|
|
Test coverage: [summary]
|
|
|
|
The forge cools. The blade rests. Ready for use.
|
|
|
|
🔥 END TRANSMISSION 🔥
|
|
"""
|