Skillswhatwg-specification-implementation-skill
W

whatwg-specification-implementation-skill

This skill provides **comprehensive guidance for implementing WHATWG specifications in Zig**: from reading the spec to writing spec-compliant Zig code.

zig-whatwg
9 stars
1.2k downloads
Updated 1w ago

Readme

whatwg-specification-implementation-skill follows the SKILL.md standard. Use the install command to add it to your agent stack.

# WHATWG Specification Implementation Skill

## Purpose

This skill provides **comprehensive guidance for implementing WHATWG specifications in Zig**: from reading the spec to writing spec-compliant Zig code.

---

## When to Use This Skill

Load this skill automatically when:
- Implementing features from any WHATWG specification
- Looking up specific algorithm steps or sections
- Mapping WHATWG spec concepts to Zig implementations
- Understanding data structures, state machines, or parsers
- Verifying correct implementation behavior
- Checking interface definitions and method signatures

---

## What This Skill Provides

**Integrated workflow for WHATWG spec implementation:**

1. **Spec Navigation** - Find and read WHATWG algorithms
2. **Context Detection** - Automatically identify which spec you're working on
3. **Type Mapping** - Map spec types to idiomatic Zig types
4. **Implementation Patterns** - Complete examples with numbered steps matching spec
5. **Cross-Spec Dependencies** - Handle dependencies between specs
6. **Spec Compliance** - Ensure implementation matches specification exactly

---

# Part 1: Reading WHATWG Specifications

## Critical Rule: Always Load Complete Spec Sections

**NEVER rely on grep fragments or partial algorithm text.**

When implementing any WHATWG feature:

1. **Detect context** - Identify which spec you're implementing
2. **Load the complete algorithm** from the relevant spec file
3. **Read the full algorithm** with all steps, context, and cross-references
4. **Check cross-spec dependencies** - Algorithms often reference other WHATWG specs
5. **Understand edge cases** - Every algorithm has corner cases and error conditions

---

## Context Detection

**This skill automatically detects which spec you're working on from:**

### File Path Detection

```
src/url/parser.zig        → URL Standard
src/encoding/decoder.zig  → Encoding Standard
src/streams/readable.zig  → Streams Standard
src/infra/strings.zig     → Infra Standard
src/console/logger.zig    → Console Standard
src/mimesniff/sniff.zig   → MIME Sniffing Standard
src/webidl/types.zig      → WebIDL
```

### Import Statement Detection

```zig
@import("url")        → URL Standard
@import("encoding")   → Encoding Standard
@import("infra")      → Infra Standard
@import("webidl")     → WebIDL
@import("streams")    → Streams Standard
```

### Manual Context

If context cannot be detected, the user will specify which spec they're implementing.

---

## Available Specifications

**Complete specifications are in `specs/` directory:**

| Spec | File | Status | Key Features |
|------|------|--------|--------------|
| **URL** | `specs/url.md` | ✅ Implemented | Parsing, serialization, host parsing |
| **Encoding** | `specs/encoding.md` | ✅ Implemented | UTF-8, legacy encodings |
| **Console** | `specs/console.md` | ✅ Implemented | Logging APIs |
| **MIME Sniff** | `specs/mimesniff.md` | ✅ Implemented | MIME type detection |
| **WebIDL** | `specs/webidl.md` | ✅ Implemented | Type system, conversions |
| **Infra** | `specs/infra.md` | ✅ Implemented | Primitives (lists, strings, bytes) |
| **Streams** | `specs/streams.md` | ✅ Implemented | ReadableStream, WritableStream |
| **Fetch** | `specs/fetch.md` | Available | HTTP requests |
| **DOM** | `specs/dom.md` | Available | DOM APIs |
| **XHR** | `specs/xhr.md` | Available | XMLHttpRequest |

---

## Workflow: Spec to Implementation

### Step 1: Identify Context

**Automatic detection:**
- Working in `src/url/` → URL Standard
- Working in `src/encoding/` → Encoding Standard
- Working in `src/streams/` → Streams Standard

**Manual context:**
- User says "I'm implementing URL parsing" → URL Standard
- User says "I need to decode UTF-8" → Encoding Standard

### Step 2: Locate the Spec File

**Pattern:** `specs/[spec-name].md`

```bash
# URL Standard
specs/url.md

# Encoding Standard
specs/encoding.md

# Streams Standard
specs/streams.md

# Infra Standard (used by almost all specs)
specs/infra.md
```

### Step 3: Find the Algorithm

**Use ripgrep (rg) to find algorithms:**

```bash
# Find URL parsing algorithm
rg -n "basic URL parser" specs/url.md

# Find encoding decoder
rg -n "decode" specs/encoding.md

# Find stream reading
rg -n "read from a readable stream" specs/streams.md

# Find Infra list operations
rg -n "append to a list" specs/infra.md
```

**Common algorithm patterns:**
- "To [operation name]" - Algorithm definitions
- "The [operation name] algorithm" - Formal algorithm sections
- "[Type] object's [operation]" - Object method algorithms

### Step 4: Load Complete Section

**Read the file** with the `read` tool:

```
read("specs/url.md", offset=<start_line>, limit=<line_count>)
```

**Find section boundaries:**
- Load from algorithm header to the next algorithm or section
- Include all numbered steps, notes, and examples
- Don't stop at the first step - read the ENTIRE algorithm

### Step 5: Understand Cross-Spec References

**WHATWG specs frequently reference each other:**

**Common patterns:**
- "Let result be an empty list" → Infra Standard (§4 Lists)
- "Let url be a new URL" → URL Standard
- "Encode using UTF-8" → Encoding Standard
- "Return a new promise" → WebIDL

**When you see cross-spec references:**
1. Note the referenced spec
2. Use `monorepo_navigation` skill to find implementation in `src/`
3. If not implemented, use `dependency_mocking` skill to create temporary mock

---

## Spec Terminology

| Spec Term | Meaning |
|-----------|---------|
| **Let** | Variable assignment |
| **Set** | Modify existing variable or property |
| **Return** | Return value from algorithm |
| **Throw** | Raise an exception |
| **Assert** | Invariant that should always be true |
| **If...then...otherwise** | Conditional logic |
| **For each...in** | Loop over collection |
| **While** | Loop with condition |
| **Abort these steps** | Early return (stop algorithm) |
| **Continue** | Skip to next iteration |

---

# Part 2: WHATWG Type Mapping to Zig

## Core Principle

Map WHATWG spec types to **idiomatic Zig** types that preserve spec semantics while enabling efficient implementation.

## General Mapping Patterns

| Spec Concept | Zig Type | Notes |
|--------------|----------|-------|
| **String** | `[]const u8` | UTF-8 encoded |
| **Byte sequence** | `[]const u8` | Raw bytes |
| **List** | `std.ArrayList(T)` | Dynamic array |
| **Ordered map** | Custom struct | Preserves insertion order |
| **Boolean** | `bool` | Direct mapping |
| **Integer** | `i32`, `u32`, `usize` | Context-dependent |
| **Float** | `f64` | Usually f64 for spec compliance |
| **Enum/State** | `enum` | For spec-defined states |
| **Record/Struct** | `struct` | For spec-defined types |
| **Optional** | `?T` | For "X or null" |

---

## Context-Specific Type Mapping

### URL Standard Types

| URL Spec Type | Zig Type | Example |
|---------------|----------|---------|
| URL record | `struct` | `pub const URL = struct { ... }` |
| Host | `union(enum)` | domain, IPv4, IPv6, opaque |
| Scheme | `[]const u8` | "https", "http", "file" |
| Port | `?u16` | Optional port (0-65535 or null) |
| Path | `[]const u8` | "/path/to/resource" |

**Example:**
```zig
/// URL record from URL Standard §4.
pub const URL = struct {
    scheme: []const u8,
    username: []const u8,
    password: []const u8,
    host: ?Host,
    port: ?u16,
    path: []const u8,
    query: ?[]const u8,
    fragment: ?[]const u8,
    allocator: Allocator,
    
    pub fn deinit(self: *URL) void {
        self.allocator.free(self.scheme);
        self.allocator.free(self.username);
        self.allocator.free(self.password);
        if (self.host) |host| host.deinit(self.allocator);
        self.allocator.free(self.path);
        if (self.query) |q| self.allocator.free(q);
        if (self.fragment) |f| self.allocator.free(f);
    }
};

/// Host from URL Standard §4.2.
pub const Host = union(enum) {
    domain: []const u8,
    ipv4: u32,
    ipv6: [8]u16,
    opaque: []const u8,
    
    pub fn deinit(self: Host, allocator: Allocator) void {
        switch (self) {
            .domain, .opaque => |s| allocator.free(s),
            .ipv4, .ipv6 => {},
        }
    }
};
```

### Encoding Standard Types

| Encoding Spec Type | Zig Type | Example |
|--------------------|----------|---------|
| Decoder | `struct` | Stateful decoder |
| Encoder | `struct` | Stateful encoder |
| Code point | `u21` | Unicode code point (0-0x10FFFF) |
| Byte | `u8` | Single byte |

**Example:**
```zig
/// Decoder from Encoding Standard.
pub const Decoder = struct {
    encoding: Encoding,
    state: DecoderState,
    allocator: Allocator,
    
    pub fn decode(self: *Decoder, input: []const u8) ![]const u8 {
        var output = std.ArrayList(u8).init(self.allocator);
        errdefer output.deinit();
        
        for (input) |byte| {
            const result = try self.processByte(byte);
            if (result.output) |code_point| {
                try appendCodePoint(&output, code_point);
            }
        }
        
        return output.toOwnedSlice();
    }
};
```

### Streams Standard Types

| Streams Spec Type | Zig Type | Example |
|-------------------|----------|---------|
| ReadableStream | `struct` | Stream state + queue |
| WritableStream | `struct` | Stream state + queue |
| Stream state | `enum` | readable/closed/errored |
| Reader/Writer | `struct` | Locks the stream |

**Example:**
```zig
/// ReadableStream from Streams Standard §3.
pub const ReadableStream = struct {
    state: State,
    reader: ?*Reader,
    queue: std.ArrayList([]const u8),
    allocator: Allocator,
    
    pub const State = enum {
        readable,
        closed,
        errored,
    };
    
    pub fn getReader(self: *ReadableStream) !*Reader {
        if (self.isLocked()) {
            return error.TypeError;
        }
        
        const reader = try self.allocator.create(Reader);
        errdefer self.allocator.destroy(reader);
        
        reader.* = Reader{
            .stream = self,
            .allocator = self.allocator,
        };
        self.reader = reader;
        
        return reader;
    }
};
```

### Infra Standard Types

| Infra Spec Type | Zig Type | Example |
|-----------------|----------|---------|
| List | `std.ArrayList(T)` | Dynamic array |
| Ordered map | Custom struct | Insertion-order map |
| Byte sequence | `[]const u8` | Raw bytes |
| Code point | `u21` | Unicode code point |
| String | `[]const u8` | UTF-8 encoded |

**Example:**
```zig
/// Ordered map from Infra Standard §7.
pub fn OrderedMap(comptime K: type, comptime V: type) type {
    return struct {
        entries: std.ArrayList(Entry),
        allocator: Allocator,
        
        const Entry = struct {
            key: K,
            value: V,
        };
        
        const Self = @This();
        
        pub fn get(self: Self, key: K) ?V {
            for (self.entries.items) |entry| {
                if (std.meta.eql(entry.key, key)) {
                    return entry.value;
                }
            }
            return null;
        }
        
        pub fn set(self: *Self, key: K, value: V) !void {
            for (self.entries.items, 0..) |entry, i| {
                if (std.meta.eql(entry.key, key)) {
                    self.entries.items[i].value = value;
                    return;
                }
            }
            try self.entries.append(.{ .key = key, .value = value });
        }
    };
}
```

---

# Part 3: Algorithm Implementation

## Step-by-Step Implementation Pattern

**Spec Algorithm:**
```
To parse a URL, given a string input:

1. Let url be a new URL.
2. Let state be scheme start state.
3. Set url's scheme to the empty string.
4. For each code point c in input:
   a. If state is scheme start state:
      i. If c is an ASCII alpha, ...
      ii. Otherwise, ...
5. Return url.
```

**Zig Implementation:**
```zig
/// Parse a URL string into a URL record.
///
/// Implements WHATWG URL "parse a URL" per §4.1.
///
/// ## Spec Reference
/// https://url.spec.whatwg.org/#concept-url-parser
///
/// ## Algorithm (URL §4.1)
/// 1. Let url be a new URL.
/// 2. Let state be scheme start state.
/// 3. Set url's scheme to the empty string.
/// 4. For each code point c in input...
/// 5. Return url.
///
/// ## Parameters
/// - `allocator`: Memory allocator
/// - `input`: URL string to parse
///
/// ## Returns
/// Parsed URL record, or error if invalid.
pub fn parse(allocator: Allocator, input: []const u8) !URL {
    // 1. Let url be a new URL
    var url = URL.init(allocator);
    errdefer url.deinit();
    
    // 2. Let state be scheme start state
    var state = State.scheme_start;
    
    // 3. Set url's scheme to the empty string
    url.scheme = "";
    
    // 4. For each code point c in input
    var iter = CodePointIterator.init(input);
    while (iter.next()) |c| {
        // a. If state is scheme start state
        if (state == .scheme_start) {
            // i. If c is an ASCII alpha
            if (isAsciiAlpha(c)) {
                // ... (implementation)
            }
            // ii. Otherwise
            else {
                // ... (implementation)
            }
        }
    }
    
    // 5. Return url
    return url;
}
```

**Key Pattern:**
- **Numbered comments** match spec steps exactly
- **Preserve spec structure** (nested if statements, loops)
- **Use spec terminology** in variable names
- **Document with spec reference**

---

## State Machine Implementation

Many WHATWG specs use state machines (URL parsing, encoding, stream operations).

**Pattern:**
```zig
pub const State = enum {
    scheme_start,
    scheme,
    no_scheme,
    special_relative_or_authority,
    // ... (all states from spec)
};

pub fn parse(allocator: Allocator, input: []const u8) !URL {
    var url = URL.init(allocator);
    errdefer url.deinit();
    
    var state = State.scheme_start;
    var buffer = std.ArrayList(u8).init(allocator);
    defer buffer.deinit();
    
    // Main state machine loop
    var iter = CodePointIterator.init(input);
    while (iter.next()) |c| {
        switch (state) {
            .scheme_start => {
                if (isAsciiAlpha(c)) {
                    try buffer.append(toLower(c));
                    state = .scheme;
                } else {
                    state = .no_scheme;
                    iter.rewind();
                }
            },
            .scheme => {
                if (isAsciiAlphanumeric(c) or c == '+' or c == '-' or c == '.') {
                    try buffer.append(toLower(c));
                } else if (c == ':') {
                    url.scheme = try buffer.toOwnedSlice();
                    state = .special_relative_or_authority;
                } else {
                    return error.InvalidScheme;
                }
            },
            // ... (all other states)
        }
    }
    
    return url;
}
```

---

## Documentation Pattern for Spec Implementations

**Every public function implementing a spec algorithm MUST have:**

```zig
/// [Brief one-line description].
///
/// Implements WHATWG [Spec Name] "[algorithm name]" per §X.Y.
///
/// [Detailed explanation]
///
/// ## Spec Reference
///
/// https://[spec].spec.whatwg.org/#[anchor]
///
/// ## Algorithm ([Spec Name] §X.Y)
///
/// [Paste the complete algorithm from the spec, or summarize steps]
///
/// ## Parameters
///
/// - `param1`: Description
/// - `param2`: Description
///
/// ## Returns
///
/// Description of return value
///
/// ## Errors
///
/// - `error.ErrorName`: When this error occurs
///
/// ## Example
///
/// ```zig
/// const result = try functionName(allocator, input);
/// defer result.deinit();
/// ```
pub fn functionName(allocator: Allocator, input: []const u8) !Result {
    // Implementation with numbered comments matching spec
}
```

---

# Part 4: Spec-Specific Guidance

## URL Standard

**Key sections:**
- §4 URL parsing - The basic URL parser (state machine)
- §4.3 Host parsing - IPv4, IPv6, domain, opaque host
- §4.5 URL serialization - Converting URL to string

**Common algorithms:**
- "basic URL parser" - Main parsing algorithm
- "host parser" - Parse host component
- "URL serializer" - Convert URL to string

**Search patterns:**
```bash
rg -n "basic URL parser" specs/url.md
rg -n "host parser" specs/url.md
rg -n "URL serializer" specs/url.md
```

## Encoding Standard

**Key sections:**
- §4 Encodings - UTF-8, legacy encodings
- §5 API - Decoder and encoder interfaces
- §8 Legacy encodings - ISO-8859-1, Shift_JIS, etc.

**Common algorithms:**
- "decode" - Convert bytes to string
- "encode" - Convert string to bytes
- "UTF-8 decode" - UTF-8 decoding algorithm

**Search patterns:**
```bash
rg -n "UTF-8 decode" specs/encoding.md
rg -n "encode" specs/encoding.md
```

## Streams Standard

**Key sections:**
- §3 Readable streams - ReadableStream, readers, controllers
- §4 Writable streams - WritableStream, writers, controllers
- §5 Transform streams - TransformStream
- §6 Queuing strategies - Buffering and backpressure

**Common algorithms:**
- "read from a readable stream" - Reading chunks
- "write to a writable stream" - Writing chunks
- "pipe to" - Connecting streams

**Search patterns:**
```bash
rg -n "read from a readable stream" specs/streams.md
rg -n "write to a writable stream" specs/streams.md
rg -n "pipe to" specs/streams.md
```

## Infra Standard

**Key sections:**
- §2 Bytes - Byte sequences and operations
- §3 Code points - Unicode code point operations
- §4 Lists - List/array operations
- §5 Strings - String operations
- §7 Ordered maps - Key-value maps with insertion order

**Common algorithms:**
- "append to a list" - Add item to list
- "ASCII lowercase" - Lowercase ASCII string
- "split a string" - String splitting

**Search patterns:**
```bash
rg -n "append to a list" specs/infra.md
rg -n "ASCII lowercase" specs/infra.md
rg -n "split a string" specs/infra.md
```

---

# Part 5: Cross-Spec Dependencies

## Common Dependency Patterns

**Infra** - Used by nearly all specs:
- String operations (ASCII lowercase, split, strip)
- Byte operations
- List operations (append, prepend, remove)
- Ordered map (insertion-order key-value map)

**WebIDL** - Used by specs with Web APIs:
- Type conversions (DOMString, USVString, etc.)
- Interface definitions

**URL** - Used by Fetch, DOM, and many others:
- URL parsing
- URL serialization
- Host parsing

**Encoding** - Used by text processing specs:
- UTF-8 encoding/decoding
- Legacy encoding support

**Streams** - Used by Fetch, File API, etc.:
- ReadableStream
- WritableStream
- TransformStream

## Handling Dependencies

**When spec references another spec:**

1. **Identify dependency** - Note which spec is referenced
2. **Check if implemented** - Use `ls src/[spec-name]/`
3. **Import if available** - `@import("spec-name")`
4. **Mock if unavailable** - Use `dependency_mocking` skill

**Example:**
```zig
// Spec says: "Let result be an empty list"
// This references Infra Standard

const infra = @import("infra");

pub fn parseURL(allocator: Allocator, input: []const u8) !URL {
    // "Let segments be an empty list"
    var segments = std.ArrayList([]const u8).init(allocator);
    defer segments.deinit();
    
    // Use Infra operations if needed
    // try infra.append(&segments, segment);
}
```

---

# Part 6: WebIDL Interface Naming Conventions

## Critical Rules for WebIDL Interfaces

**When implementing WebIDL interfaces, namespaces, mixins, or dictionaries:**

### File Naming: PascalCase

**All WebIDL interface files MUST use PascalCase naming:**

```
❌ WRONG:
webidl/src/encoding/text_decoder.zig
webidl/src/console/console.zig
webidl/src/dom/abort_controller.zig

✅ CORRECT:
webidl/src/encoding/TextDecoder.zig
webidl/src/console/Console.zig
webidl/src/dom/AbortController.zig
```

**This applies to:**
- **Interfaces**: `TextDecoder.zig`, `ReadableStream.zig`, `EventTarget.zig`
- **Namespaces**: `Console.zig`
- **Mixins**: `TextDecoderCommon.zig`, `EventTargetMixin.zig`
- **Dictionaries**: `TextDecoderOptions.zig`, `ReadableStreamReadResult.zig`

**Rationale:** File names should match the type they define for clarity and consistency.

### Attribute Naming: camelCase

**All WebIDL interface attributes MUST use camelCase:**

```zig
❌ WRONG:
pub const TextDecoder = webidl.interface(struct {
    encoding_name: []const u8,      // ❌ snake_case
    do_not_flush: bool,              // ❌ snake_case
    ignore_bom: bool,                // ❌ snake_case
    reusable_utf16_buf: ?[]u16,     // ❌ snake_case
});

✅ CORRECT:
pub const TextDecoder = webidl.interface(struct {
    encodingName: []const u8,                // ✅ camelCase
    doNotFlush: bool,                        // ✅ camelCase
    ignoreBOM: bool,                         // ✅ camelCase
    reusableUtf16Buffer: ?webidl.DOMString, // ✅ camelCase + WebIDL type
});
```

**Rationale:** WebIDL attributes use camelCase per JavaScript naming conventions.

### Method Naming: camelCase (No Prefixes)

**WebIDL methods MUST use camelCase without prefixes:**

```zig
❌ WRONG:
pub fn call_decode(...) ![]const u8 { }    // ❌ call_ prefix
pub fn call_encode(...) ![]const u8 { }    // ❌ call_ prefix
pub fn get_encoding() []const u8 { }       // ❌ get_ prefix for getter
pub fn call_log(...) void { }              // ❌ call_ prefix

✅ CORRECT:
pub fn decode(...) ![]const u8 { }         // ✅ No prefix
pub fn encode(...) ![]const u8 { }         // ✅ No prefix
pub fn encoding() []const u8 { }           // ✅ No prefix (readonly attribute)
pub fn log(...) void { }                   // ✅ No prefix
```

**Exception for getters:** Readonly attributes may use method names matching the attribute:
- `readonly attribute DOMString encoding` → `pub fn encoding() []const u8`
- `readonly attribute boolean fatal` → `pub fn getFatal() bool` (or just expose field)

**Rationale:** WebIDL methods map directly to JavaScript methods, which use camelCase without prefixes.

### Type Usage: WebIDL/Infra Types

**ALWAYS use WebIDL/Infra type aliases instead of raw Zig primitives:**

```zig
❌ WRONG (raw Zig types):
pub const TextDecoder = webidl.interface(struct {
    fatal: bool,                   // ❌ Use webidl.boolean
    reusable_buf: ?[]u16,          // ❌ Use webidl.DOMString
    encoding_name: []const u8,     // ❌ For WebIDL string, consider DOMString
});

✅ CORRECT (WebIDL types):
pub const TextDecoder = webidl.interface(struct {
    fatal: webidl.boolean,                 // ✅ WebIDL type
    reusableBuffer: ?webidl.DOMString,     // ✅ WebIDL type (UTF-16)
    encodingName: []const u8,              // ✅ OK for internal UTF-8
});
```

**Available WebIDL types** (from `src/webidl/root.zig`):

```zig
// Primitives
webidl.boolean           // bool
webidl.long              // i32
webidl.@"unsigned long"  // u32
webidl.double            // f64
webidl.any               // JSValue

// Strings
webidl.DOMString         // UTF-16 string (infra.String)
webidl.USVString         // UTF-16 scalar values (infra.String)
webidl.ByteString        // Latin-1 string ([]const u8)

// Complex types
webidl.JSValue           // JavaScript value union
webidl.JSObject          // JavaScript object
webidl.Nullable(T)       // T or null
webidl.Sequence(T)       // Array of T
webidl.Record(K, V)      // Map/dictionary
```

**When to use WebIDL types vs Zig primitives:**

| Use Case | Type Choice | Example |
|----------|-------------|---------|
| **WebIDL interface parameter** | WebIDL type | `fn decode(input: webidl.ByteString)` |
| **WebIDL interface attribute** | WebIDL type | `fatal: webidl.boolean` |
| **WebIDL interface return** | WebIDL type | `fn encode() !webidl.ByteString` |
| **Internal implementation** | Zig primitive OK | `var index: usize = 0;` |
| **Spec algorithm variable** | Zig primitive OK | `var found: bool = false;` |

**Key principle:** Use WebIDL types at API boundaries, Zig primitives for internal implementation.

### Complete Example

**BEFORE (incorrect naming):**
```zig
// ❌ webidl/src/encoding/text_decoder.zig

const TextDecoderOptions = @import("text_decoder_options.zig").TextDecoderOptions;

pub const TextDecoder = webidl.interface(struct {
    encoding_name: []const u8,     // ❌ snake_case
    do_not_flush: bool,             // ❌ snake_case, raw type
    ignore_bom: bool,               // ❌ snake_case, raw type
    
    pub fn get_encoding(self: *const TextDecoder) []const u8 {  // ❌ get_ prefix
        return self.encoding_name;
    }
    
    pub fn call_decode(                 // ❌ call_ prefix
        self: *TextDecoder,
        input: []const u8,
        options: TextDecodeOptions,
    ) ![]const u8 {
        // ...
    }
});
```

**AFTER (correct naming):**
```zig
// ✅ webidl/src/encoding/TextDecoder.zig

const TextDecoderOptions = @import("TextDecoderOptions.zig").TextDecoderOptions;

pub const TextDecoder = webidl.interface(struct {
    encodingName: []const u8,              // ✅ camelCase
    doNotFlush: webidl.boolean,            // ✅ camelCase + WebIDL type
    ignoreBOM: webidl.boolean,             // ✅ camelCase + WebIDL type
    
    pub fn encoding(self: *const TextDecoder) []const u8 {  // ✅ No prefix
        return self.encodingName;
    }
    
    pub fn decode(                          // ✅ No prefix
        self: *TextDecoder,
        input: []const u8,
        options: TextDecodeOptions,
    ) ![]const u8 {
        // ...
    }
});
```

### Import Statements

**Update imports to use PascalCase filenames:**

```zig
❌ WRONG:
const TextDecoder = @import("text_decoder.zig").TextDecoder;
const TextDecoderOptions = @import("text_decoder_options.zig").TextDecoderOptions;

✅ CORRECT:
const TextDecoder = @import("TextDecoder.zig").TextDecoder;
const TextDecoderOptions = @import("TextDecoderOptions.zig").TextDecoderOptions;
```

### WebIDL Mixins

**Interface mixins define reusable member bundles that can be included in multiple interfaces using composition.**

#### What are Mixins?

From WebIDL spec:
> An interface mixin is a definition (matching `InterfaceMixin`) that declares a set of members that can be included by one or more interfaces.

Mixins are used to share common attributes and methods across multiple interfaces without inheritance.

#### Critical Rule: Always Use `webidl.mixin()`

**ALL mixin definitions MUST use `webidl.mixin()` wrapper:**

```zig
// IDL:
// interface mixin TextDecoderCommon {
//   readonly attribute DOMString encoding;
//   readonly attribute boolean fatal;
//   readonly attribute boolean ignoreBOM;
// };

// ✅ CORRECT: Use webidl.mixin()
pub const TextDecoderCommon = webidl.mixin(struct {
    encoding: []const u8,
    fatal: webidl.boolean,
    ignoreBOM: webidl.boolean,
});

// ❌ WRONG: Don't use plain struct or webidl.interface()
pub const TextDecoderCommon = struct { ... };  // ❌ Missing webidl.mixin()
pub const TextDecoderCommon = webidl.interface(struct { ... });  // ❌ Wrong wrapper
```

**Key differences from `webidl.interface()`:**
- Mixins **cannot be instantiated** directly
- Mixins **define shared members** that are included in other interfaces
- Mixins **do not have constructors**
- Mixins are **included** using the WebIDL `includes` statement
- **MUST be wrapped with `webidl.mixin()`**

#### Mixin Composition Pattern (REQUIRED)

**When an interface includes a mixin, use composition by embedding the mixin as a field:**

```zig
// IDL:
// interface TextDecoder {
//   constructor(...);
//   // ... TextDecoder-specific members
// };
// TextDecoder includes TextDecoderCommon;

// ✅ CORRECT: Embed mixin as a field
pub const TextDecoder = webidl.interface(struct {
    mixin: TextDecoderCommon,  // ✅ Embed mixin as field
    
    allocator: std.mem.Allocator,
    enc: *const Encoding,
    doNotFlush: bool,
    bomSeen: bool,
    // ... other interface-specific fields
    
    pub fn init(allocator: std.mem.Allocator, label: []const u8, options: TextDecoderOptions) !TextDecoder {
        const enc = try getEncoding(label);
        return .{
            .mixin = .{  // ✅ Initialize mixin
                .encoding = enc.name,
                .fatal = options.fatal,
                .ignoreBOM = options.ignoreBOM,
            },
            .allocator = allocator,
            .enc = enc,
            .doNotFlush = false,
            .bomSeen = false,
        };
    }
    
    // ✅ Delegate getters to mixin
    pub inline fn encoding(self: *const TextDecoder) []const u8 {
        return self.mixin.encoding;
    }
    
    pub inline fn getFatal(self: *const TextDecoder) webidl.boolean {
        return self.mixin.fatal;
    }
    
    pub inline fn getIgnoreBOM(self: *const TextDecoder) webidl.boolean {
        return self.mixin.ignoreBOM;
    }
});
```

**❌ WRONG: Direct field duplication (DO NOT DO THIS):**
```zig
// ❌ Don't duplicate mixin fields directly in the interface
pub const TextDecoder = webidl.interface(struct {
    // ❌ These should be in a mixin field, not duplicated
    encoding: []const u8,       
    fatal: webidl.boolean,      
    ignoreBOM: webidl.boolean,  
    
    allocator: std.mem.Allocator,
    // ...
});
```

#### Multiple Mixin Composition

**When an interface includes multiple mixins, use descriptive field names:**

```zig
// IDL:
// interface TextDecoderStream {
//   constructor(...);
// };
// TextDecoderStream includes TextDecoderCommon;
// TextDecoderStream includes GenericTransformStream;

pub const TextDecoderStream = webidl.interface(struct {
    decoderMixin: TextDecoderCommon,        // ✅ First mixin
    transformMixin: GenericTransformStream, // ✅ Second mixin
    
    allocator: std.mem.Allocator,
    // ... other fields
    
    pub fn init(...) !TextDecoderStream {
        return .{
            .decoderMixin = .{
                .encoding = enc.name,
                .fatal = options.fatal,
                .ignoreBOM = options.ignoreBOM,
            },
            .transformMixin = .{
                .transform = transform,
            },
            // ... other fields
        };
    }
    
    // Delegate to TextDecoderCommon mixin
    pub inline fn encoding(self: *const TextDecoderStream) []const u8 {
        return self.decoderMixin.encoding;
    }
    
    // Delegate to GenericTransformStream mixin
    pub inline fn readable(self: *const TextDecoderStream) *ReadableStream {
        return self.transformMixin.readable();
    }
});
```

#### File Naming for Mixins

Mixin files follow the same PascalCase naming as interfaces:

```
❌ WRONG: text_decoder_common.zig
✅ CORRECT: TextDecoderCommon.zig
```

#### Verifying Mixin Usage in Specs

**When implementing an interface, ALWAYS check the spec for `includes` statements:**

```
// In specs/encoding.md:
TextDecoder includes TextDecoderCommon;
TextDecoderStream includes TextDecoderCommon;
```

**Steps to implement:**
1. Find all `includes` statements for the interface in the spec
2. Verify mixin definitions exist and use `webidl.mixin()`
3. Embed each mixin as a field in the interface
4. Delegate all mixin methods/getters to the mixin fields
5. Never duplicate mixin fields directly in the interface

#### Common WebIDL Mixins (Verified)

From the WHATWG specs:
- **TextDecoderCommon** (`webidl/src/encoding/TextDecoderCommon.zig`): Shared by `TextDecoder` and `TextDecoderStream` ✅
- **TextEncoderCommon** (`webidl/src/encoding/TextEncoderCommon.zig`): Shared by `TextEncoder` and `TextEncoderStream` ✅
- **GenericTransformStream** (`webidl/src/streams/GenericTransformStream.zig`): Shared by transform stream classes ✅
- **ReadableStreamGenericReader** (`webidl/src/streams/ReadableStreamGenericReader.zig`): Shared by reader classes ✅
- **WindowOrWorkerGlobalScope**: Shared by `Window` and `WorkerGlobalScope`
- **Body**: Shared by `Request` and `Response` (Fetch API)

#### Complete Mixin Example

**Mixin Definition (`TextEncoderCommon.zig`):**
```zig
const webidl = @import("webidl");

pub const TextEncoderCommon = webidl.mixin(struct {  // ✅ MUST use webidl.mixin()
    /// The encoding name (always "utf-8")
    encoding: []const u8,
});
```

**Interface Including Mixin (`TextEncoder.zig`):**
```zig
const webidl = @import("webidl");
const TextEncoderCommon = @import("TextEncoderCommon.zig").TextEncoderCommon;

pub const TextEncoder = webidl.interface(struct {
    mixin: TextEncoderCommon,  // ✅ Embed mixin as field
    allocator: std.mem.Allocator,
    
    pub fn init(allocator: std.mem.Allocator) TextEncoder {
        return .{
            .mixin = .{  // ✅ Initialize mixin
                .encoding = "utf-8",
            },
            .allocator = allocator,
        };
    }
    
    pub fn deinit(self: *TextEncoder) void {
        _ = self;
    }
    
    // ✅ Delegate getter to mixin
    pub inline fn encoding(self: *const TextEncoder) []const u8 {
        return self.mixin.encoding;
    }
    
    pub fn encode(self: *TextEncoder, input: []const u8) ![]const u8 {
        // Implementation
    }
});
```

#### Key Principles for Mixin Implementation

**✅ DO:**
1. **Always use `webidl.mixin()`** for mixin definitions
2. **Embed mixins as fields** in interfaces that include them
3. **Use descriptive field names** for multiple mixins (`decoderMixin`, `transformMixin`)
4. **Delegate all getters** from interface to mixin fields
5. **Initialize mixin fields** in constructor
6. **Check spec for `includes` statements** to find all required mixins
7. **Never duplicate mixin fields** directly in the interface struct

**❌ DON'T:**
1. Don't use plain `struct` for mixins (must use `webidl.mixin()`)
2. Don't use `webidl.interface()` for mixins (wrong wrapper)
3. Don't duplicate mixin fields directly in interface
4. Don't skip delegation - always delegate to mixin
5. Don't use generic names like `mixin1`, `mixin2` for multiple mixins
6. Don't bypass mixin fields with direct access

### Checklist for WebIDL Interfaces and Mixins

When creating or modifying WebIDL interfaces or mixins:

**General Requirements:**
- [ ] **Identify the WebIDL type**:
  - [ ] Use `webidl.interface()` for IDL `interface` definitions
  - [ ] Use `webidl.namespace()` for IDL `namespace` definitions
  - [ ] Use `webidl.mixin()` for IDL `interface mixin` definitions (REQUIRED)
- [ ] **File named in PascalCase** (e.g., `TextDecoder.zig`, not `text_decoder.zig`)
- [ ] **All attributes use camelCase** (e.g., `encodingName`, not `encoding_name`)
- [ ] **All methods use camelCase without prefixes** (e.g., `decode()`, not `call_decode()`)
- [ ] **Use WebIDL type aliases** (e.g., `webidl.boolean`, not `bool` for attributes)
- [ ] **Use WebIDL string types appropriately** (e.g., `webidl.DOMString` for UTF-16)
- [ ] **Import statements use PascalCase filenames**
- [ ] **Tests updated to use camelCase method names**
- [ ] **Documentation uses correct naming**

**For Mixin Definitions:**
- [ ] **MUST use `webidl.mixin()` wrapper** (not plain struct or webidl.interface)
- [ ] **Define only shared attributes** (no constructors, no instance-specific fields)
- [ ] **Use camelCase for mixin fields**
- [ ] **Document which interfaces include this mixin**

**For Interfaces Including Mixins:**
- [ ] **Check spec for ALL `includes` statements** for this interface
- [ ] **Embed each mixin as a field** (e.g., `mixin: TextDecoderCommon`)
- [ ] **Use descriptive field names** for multiple mixins (e.g., `decoderMixin`, `transformMixin`)
- [ ] **Initialize mixin fields in constructor**
- [ ] **Add delegation methods** for all mixin getters (e.g., `pub inline fn encoding(self) { return self.mixin.encoding; }`)
- [ ] **Never duplicate mixin fields** directly in the interface struct
- [ ] **Update all internal references** to use `self.mixin.field` instead of `self.field`

---

# Quick Reference

## Workflow Summary

1. **Identify context** - Which spec am I implementing?
2. **Find algorithm** - `rg -n "algorithm name" specs/[spec].md`
3. **Load complete section** - Read entire algorithm with context
4. **Map types to Zig** - Use type mapping tables above
5. **Implement with numbered comments** - Match spec steps exactly
6. **Document with spec reference** - Include URL and algorithm
7. **Test** - Verify against spec behavior

## Common Searches

```bash
# URL
rg -n "basic URL parser" specs/url.md
rg -n "host parser" specs/url.md

# Encoding
rg -n "UTF-8 decode" specs/encoding.md
rg -n "encode" specs/encoding.md

# Streams
rg -n "read from a readable stream" specs/streams.md
rg -n "write to a writable stream" specs/streams.md

# Infra
rg -n "append to a list" specs/infra.md
rg -n "ASCII lowercase" specs/infra.md
```

## Type Mapping Quick Lookup

```
String              → []const u8
Byte sequence       → []const u8
List<T>             → std.ArrayList(T)
Ordered map         → Custom struct (Infra pattern)
Boolean             → bool
Integer             → i32, u32, usize (context-dependent)
Optional<T>         → ?T
Enum/State          → enum
Record/Struct       → struct
```

---

## Integration with Other Skills

This skill works with:
- **zig** - Provides Zig idioms for implementing algorithms
- **monorepo_navigation** - Finds cross-spec dependencies
- **dependency_mocking** - Creates mocks for unimplemented dependencies

---

**Remember**: Read complete spec sections, map types precisely, implement with numbered comments matching spec steps, and always reference the specification in documentation.

Install

Requires askill CLI v1.0+

Metadata

LicenseUnknown
Version-
Updated1w ago
Publisherzig-whatwg

Tags

apigithub-actionsobservabilitytesting