Skip to content

Commit 73881e3

Browse files
cloutiertylerclockwork-labs-botjdetter
authored
Further misc docs changes (#4029)
# Description of Changes Major documentation overhaul focusing on tables, column types, and indexes. **Quickstart Guides:** - Updated React, TypeScript, Rust, and C# quickstarts with table/reducer examples - Fixed CLI syntax (positional `--database` argument) - Improved template consistency across languages **Tables Documentation:** - Added "Why Tables" section explaining table-oriented design philosophy (tables as fundamental unit, system tables, data-oriented design principles) - Added "Physical and Logical Independence" section explaining how subscription queries use the relational model independently of physical storage - Added brief sections linking to related pages (Visibility, Constraints, Schedule Tables) - Renamed "Scheduled Tables" to "Schedule Tables" throughout (tables store schedules; reducers are scheduled) **Column Types:** - Split into dedicated page with unified type reference table - Added "Representing Collections" section (Vec/Array vs table tradeoffs) - Added "Binary Data and Files" section for Vec<u8> storage patterns - Added "Type Performance" section (smaller types, fixed-size types, column ordering for alignment) - Added complete example struct demonstrating all type categories - Renamed "Structured" category to "Composite" **Indexes:** - Complete rewrite with textbook-style documentation - Added "When to Use Indexes" guidance - Documented single-column and multi-column index syntax (field-level and table-level) - Comprehensive range query examples with correct TypeScript `Range` class syntax - Explained multi-column index prefix matching semantics - Added index-accelerated deletion examples - Included index design guidelines **Styling:** - Added CSS for table border radius and row separators - Created Check component for green checkmarks in tables # API and ABI breaking changes None. Documentation only. # Expected complexity level and risk 1 - Documentation changes only, no code changes. # Testing - [ ] Verify docs build without errors - [ ] Review rendered pages for formatting issues - [ ] Confirm code examples are syntactically correct --------- Signed-off-by: Tyler Cloutier <cloutiertyler@users.noreply.github.com> Signed-off-by: John Detter <4099508+jdetter@users.noreply.github.com> Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com> Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com>
1 parent 38ee9e8 commit 73881e3

104 files changed

Lines changed: 6401 additions & 1603 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/llm-benchmark-update.yml

Lines changed: 41 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ jobs:
2828
if: |
2929
(github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/update-llm-benchmark')) ||
3030
(github.event_name == 'workflow_dispatch')
31-
runs-on: ubuntu-latest
31+
runs-on: spacetimedb-new-runner
32+
container:
33+
image: localhost:5000/spacetimedb-ci:latest
34+
options: >-
35+
--privileged
3236
steps:
3337
# Here we install the spacetime CLI for faster execution of the tests
3438
# SpacetimeDB itself is not under test here, rather it's the docs.
@@ -201,6 +205,18 @@ jobs:
201205
llm_benchmark ci-quickfix
202206
llm_benchmark ci-check
203207
208+
# Generate failure analysis if there are any failures
209+
- name: Generate failure analysis
210+
env:
211+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
212+
run: |
213+
llm_benchmark analyze -o docs/llms/docs-benchmark-analysis.md || true
214+
215+
# Generate PR comment markdown (compares against master baseline)
216+
- name: Generate PR comment markdown
217+
run: |
218+
llm_benchmark ci-comment
219+
204220
- name: Ensure only docs/llms changed
205221
run: |
206222
set -euo pipefail
@@ -226,77 +242,41 @@ jobs:
226242
github-token: ${{ secrets.CLOCKWORK_LABS_BOT_PAT }}
227243
script: |
228244
const fs = require('fs');
229-
// docs-benchmark files are used for CI (testing documentation quality)
230-
const summaryPath = 'docs/llms/docs-benchmark-summary.json';
231-
const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
232-
233-
// Extract results for the modes checked by ci-check
234-
// Rust: rustdoc_json, C#: docs
235-
const rustResults = summary.by_language?.rust?.modes?.rustdoc_json?.models?.['GPT-5'];
236-
const csharpResults = summary.by_language?.csharp?.modes?.docs?.models?.['GPT-5'];
237-
238-
const formatPct = (val) => val !== undefined ? `${val.toFixed(1)}%` : 'N/A';
239-
240-
let table = `## LLM Benchmark Results (ci-quickfix)\n\n`;
241-
table += `| Language | Mode | Category | Tests Passed | Pass % | Task Pass % |\n`;
242-
table += `|----------|------|----------|--------------|--------|-------------|\n`;
243-
244-
if (rustResults) {
245-
const cats = rustResults.categories || {};
246-
if (cats.basics) {
247-
const c = cats.basics;
248-
table += `| Rust | rustdoc_json | basics | ${c.passed_tests}/${c.total_tests} | ${formatPct(c.pass_pct)} | ${formatPct(c.task_pass_pct)} |\n`;
249-
}
250-
if (cats.schema) {
251-
const c = cats.schema;
252-
table += `| Rust | rustdoc_json | schema | ${c.passed_tests}/${c.total_tests} | ${formatPct(c.pass_pct)} | ${formatPct(c.task_pass_pct)} |\n`;
253-
}
254-
const t = rustResults.totals;
255-
table += `| Rust | rustdoc_json | **total** | ${t.passed_tests}/${t.total_tests} | ${formatPct(t.pass_pct)} | ${formatPct(t.task_pass_pct)} |\n`;
256-
}
257245
258-
if (csharpResults) {
259-
const cats = csharpResults.categories || {};
260-
if (cats.basics) {
261-
const c = cats.basics;
262-
table += `| C# | docs | basics | ${c.passed_tests}/${c.total_tests} | ${formatPct(c.pass_pct)} | ${formatPct(c.task_pass_pct)} |\n`;
263-
}
264-
if (cats.schema) {
265-
const c = cats.schema;
266-
table += `| C# | docs | schema | ${c.passed_tests}/${c.total_tests} | ${formatPct(c.pass_pct)} | ${formatPct(c.task_pass_pct)} |\n`;
246+
// Read the pre-generated comment markdown
247+
const commentPath = 'docs/llms/docs-benchmark-comment.md';
248+
if (!fs.existsSync(commentPath)) {
249+
core.setFailed(`Comment file not found: ${commentPath}`);
250+
return;
251+
}
252+
let body = fs.readFileSync(commentPath, 'utf8');
253+
254+
// Check if failure analysis exists and append it
255+
const analysisPath = 'docs/llms/docs-benchmark-analysis.md';
256+
if (fs.existsSync(analysisPath)) {
257+
const analysis = fs.readFileSync(analysisPath, 'utf8');
258+
// Only include if there's meaningful content (not just "no failures")
259+
if (!analysis.includes('No failures found')) {
260+
body += `\n<details>\n<summary>Failure Analysis (click to expand)</summary>\n\n${analysis}\n</details>`;
267261
}
268-
const t = csharpResults.totals;
269-
table += `| C# | docs | **total** | ${t.passed_tests}/${t.total_tests} | ${formatPct(t.pass_pct)} | ${formatPct(t.task_pass_pct)} |\n`;
270262
}
271263
272-
table += `\n<sub>Generated at: ${summary.generated_at}</sub>`;
273-
274264
const issue_number = Number(process.env.PR_NUMBER);
275265
276-
// Find and update existing comment or create new one
277-
const comments = await github.rest.issues.listComments({
278-
owner: context.repo.owner,
279-
repo: context.repo.repo,
280-
issue_number,
281-
});
282-
283-
const marker = '## LLM Benchmark Results (ci-quickfix)';
284-
const existingComment = comments.data.find(c => c.body.startsWith(marker));
285-
286-
if (existingComment) {
287-
await github.rest.issues.updateComment({
288-
owner: context.repo.owner,
289-
repo: context.repo.repo,
290-
comment_id: existingComment.id,
291-
body: table,
292-
});
293-
} else {
266+
// Always post a new comment
267+
console.log(`Posting new comment on PR #${issue_number}...`);
268+
try {
294269
await github.rest.issues.createComment({
295270
owner: context.repo.owner,
296271
repo: context.repo.repo,
297272
issue_number,
298-
body: table,
273+
body,
299274
});
275+
console.log('Comment created successfully');
276+
} catch (err) {
277+
console.error('Failed to post comment:', err.message);
278+
console.error('Full error:', JSON.stringify(err, null, 2));
279+
throw err;
300280
}
301281
302282
# The benchmarks only modify the docs/llms directory.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cli/build.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ fn get_manifest_dir() -> PathBuf {
5858
// templates list at templates/templates-list.json
5959
// * `get_ai_rules_base` - returns base AI rules for all languages
6060
// * `get_ai_rules_typescript` - returns TypeScript-specific AI rules
61+
// * `get_ai_rules_rust` - returns Rust-specific AI rules
62+
// * `get_ai_rules_csharp` - returns C#-specific AI rules
6163
fn generate_template_files() {
6264
let manifest_dir = get_manifest_dir();
6365
let repo_root = get_repo_root();
@@ -140,6 +142,34 @@ fn generate_template_files() {
140142
panic!("Could not find \"docs/static/ai-rules/spacetimedb-typescript.mdc\" file.");
141143
}
142144

145+
// Rust-specific rules
146+
let rust_rules_path = ai_rules_dir.join("spacetimedb-rust.mdc");
147+
if rust_rules_path.exists() {
148+
generated_code.push_str("pub fn get_ai_rules_rust() -> &'static str {\n");
149+
generated_code.push_str(&format!(
150+
" include_str!(\"{}\")\n",
151+
rust_rules_path.to_str().unwrap().replace('\\', "\\\\")
152+
));
153+
generated_code.push_str("}\n\n");
154+
println!("cargo:rerun-if-changed={}", rust_rules_path.display());
155+
} else {
156+
panic!("Could not find \"docs/static/ai-rules/spacetimedb-rust.mdc\" file.");
157+
}
158+
159+
// C#-specific rules
160+
let csharp_rules_path = ai_rules_dir.join("spacetimedb-csharp.mdc");
161+
if csharp_rules_path.exists() {
162+
generated_code.push_str("pub fn get_ai_rules_csharp() -> &'static str {\n");
163+
generated_code.push_str(&format!(
164+
" include_str!(\"{}\")\n",
165+
csharp_rules_path.to_str().unwrap().replace('\\', "\\\\")
166+
));
167+
generated_code.push_str("}\n\n");
168+
println!("cargo:rerun-if-changed={}", csharp_rules_path.display());
169+
} else {
170+
panic!("Could not find \"docs/static/ai-rules/spacetimedb-csharp.mdc\" file.");
171+
}
172+
143173
// Expose workspace metadata so `spacetime init` can rewrite template manifests without hardcoding versions.
144174
generated_code.push_str("pub fn get_workspace_edition() -> &'static str {\n");
145175
generated_code.push_str(&format!(" \"{}\"\n", workspace_edition.escape_default()));

crates/cli/src/subcommands/init.rs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1667,15 +1667,22 @@ fn set_dependency_version(item: &mut Item, version: &str, remove_path: bool) {
16671667
/// Writes rules to:
16681668
/// - .cursor/rules/ (Cursor)
16691669
/// - CLAUDE.md (Claude Code)
1670+
/// - AGENTS.md (Opencode)
16701671
/// - .windsurfrules (Windsurf)
16711672
/// - .github/copilot-instructions.md (VS Code Copilot)
16721673
fn install_ai_rules(config: &TemplateConfig, project_path: &Path) -> anyhow::Result<()> {
16731674
let base_rules = embedded::get_ai_rules_base();
16741675
let ts_rules = embedded::get_ai_rules_typescript();
1676+
let rust_rules = embedded::get_ai_rules_rust();
1677+
let csharp_rules = embedded::get_ai_rules_csharp();
16751678

1676-
// Check if TypeScript is used in either server or client
1679+
// Check which languages are used in server or client
16771680
let uses_typescript = config.server_lang == Some(ServerLanguage::TypeScript)
16781681
|| config.client_lang == Some(ClientLanguage::TypeScript);
1682+
let uses_rust =
1683+
config.server_lang == Some(ServerLanguage::Rust) || config.client_lang == Some(ClientLanguage::Rust);
1684+
let uses_csharp =
1685+
config.server_lang == Some(ServerLanguage::Csharp) || config.client_lang == Some(ClientLanguage::Csharp);
16791686

16801687
// 1. Cursor: .cursor/rules/ directory with separate files
16811688
let cursor_dir = project_path.join(".cursor/rules");
@@ -1684,24 +1691,44 @@ fn install_ai_rules(config: &TemplateConfig, project_path: &Path) -> anyhow::Res
16841691
if uses_typescript {
16851692
fs::write(cursor_dir.join("spacetimedb-typescript.mdc"), ts_rules)?;
16861693
}
1694+
if uses_rust {
1695+
fs::write(cursor_dir.join("spacetimedb-rust.mdc"), rust_rules)?;
1696+
}
1697+
if uses_csharp {
1698+
fs::write(cursor_dir.join("spacetimedb-csharp.mdc"), csharp_rules)?;
1699+
}
16871700

16881701
// Build combined content for single-file AI assistants
16891702
// Strip the YAML frontmatter from the .mdc files for non-Cursor tools
16901703
let base_content = strip_mdc_frontmatter(base_rules);
1691-
let combined_content = if uses_typescript {
1704+
let mut combined_content = base_content.to_string();
1705+
1706+
if uses_typescript {
16921707
let ts_content = strip_mdc_frontmatter(ts_rules);
1693-
format!("{}\n\n{}", base_content, ts_content)
1694-
} else {
1695-
base_content.to_string()
1696-
};
1708+
combined_content.push_str("\n\n");
1709+
combined_content.push_str(ts_content);
1710+
}
1711+
if uses_rust {
1712+
let rust_content = strip_mdc_frontmatter(rust_rules);
1713+
combined_content.push_str("\n\n");
1714+
combined_content.push_str(rust_content);
1715+
}
1716+
if uses_csharp {
1717+
let csharp_content = strip_mdc_frontmatter(csharp_rules);
1718+
combined_content.push_str("\n\n");
1719+
combined_content.push_str(csharp_content);
1720+
}
16971721

16981722
// 2. Claude Code: CLAUDE.md
16991723
fs::write(project_path.join("CLAUDE.md"), &combined_content)?;
17001724

1701-
// 3. Windsurf: .windsurfrules
1725+
// 3. Opencode: AGENTS.md
1726+
fs::write(project_path.join("AGENTS.md"), &combined_content)?;
1727+
1728+
// 4. Windsurf: .windsurfrules
17021729
fs::write(project_path.join(".windsurfrules"), &combined_content)?;
17031730

1704-
// 4. VS Code Copilot: .github/copilot-instructions.md
1731+
// 5. VS Code Copilot: .github/copilot-instructions.md
17051732
let github_dir = project_path.join(".github");
17061733
fs::create_dir_all(&github_dir)?;
17071734
fs::write(github_dir.join("copilot-instructions.md"), &combined_content)?;

docs/DEVELOP.md

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ This document explains how to configure the environment, run the LLM benchmark t
99
1. [Quick Checks & Fixes](#quick-checks-fixes)
1010
2. [Environment Variables](#environment-variables)
1111
3. [Benchmark Suite](#benchmark-suite)
12-
4. [Troubleshooting](#troubleshooting)
12+
4. [Context Construction](#context-construction)
13+
5. [Troubleshooting](#troubleshooting)
1314
---
1415

1516
## Quick Checks & Fixes
@@ -233,6 +234,11 @@ cargo llm run --force
233234
cargo llm ci-check --lang rust
234235
cargo llm ci-check --lang csharp
235236

237+
# Generate PR comment markdown (compares against master baseline)
238+
cargo llm ci-comment
239+
# With custom baseline ref
240+
cargo llm ci-comment --baseline-ref origin/main
241+
236242
```
237243

238244
Outputs:
@@ -241,6 +247,62 @@ Outputs:
241247

242248
---
243249

250+
## Context Construction
251+
252+
The benchmark tool constructs a context (documentation) that is sent to the LLM along with each task prompt. The context varies by language and mode.
253+
254+
### Modes
255+
256+
| Mode | Language | Source | Description |
257+
|------|----------|--------|-------------|
258+
| `rustdoc_json` | Rust | `crates/bindings` | Generates rustdoc JSON and extracts documentation from the spacetimedb crate |
259+
| `docs` | C# | `docs/docs/**/*.md` | Concatenates all markdown files from the documentation |
260+
261+
### Tab Filtering
262+
263+
When building context for a specific language, the tool filters `<Tabs>` components to only include content relevant to the target language. This reduces noise and helps the LLM focus on the correct syntax.
264+
265+
**Filtered tab groupIds:**
266+
267+
| groupId | Purpose | Tab Values |
268+
|---------|---------|------------|
269+
| `server-language` | Server module code examples | `rust`, `csharp`, `typescript` |
270+
| `client-language` | Client SDK code examples | `rust`, `csharp`, `typescript`, `cpp`, `blueprint` |
271+
272+
**Filtering behavior:**
273+
- For C# tests: Only `value="csharp"` tabs are kept
274+
- For Rust tests: Only `value="rust"` tabs are kept
275+
- If no matching tab exists (e.g., `client-language` with only `cpp`/`blueprint`), the entire tabs block is removed
276+
277+
**Example transformation:**
278+
279+
Before (in markdown):
280+
```html
281+
<Tabs groupId="server-language" queryString>
282+
<TabItem value="csharp" label="C#">
283+
C# code here
284+
</TabItem>
285+
<TabItem value="rust" label="Rust">
286+
Rust code here
287+
</TabItem>
288+
</Tabs>
289+
```
290+
291+
After (for C# context):
292+
```
293+
C# code here
294+
```
295+
296+
### Documentation Best Practices
297+
298+
When writing documentation that will be used by the benchmark:
299+
300+
1. **Use consistent tab groupIds**: Always use `server-language` for server module code and `client-language` for client SDK code
301+
2. **Include all supported languages**: Ensure each `<Tabs>` block has tabs for all languages you want to test
302+
3. **Use consistent naming conventions**: The benchmark compares LLM output against golden answers, so documentation should reflect the expected conventions (e.g., PascalCase table names for C#)
303+
304+
---
305+
244306
## Troubleshooting
245307

246308
**HTTP 400/404 from providers**

docs/docs/00100-intro/00100-getting-started/00400-key-architecture.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const players = table(
4747
<TabItem value="csharp" label="C#">
4848

4949
```csharp
50-
[SpacetimeDB.Table(Name = "players", Public = true)]
50+
[SpacetimeDB.Table(Name = "Player", Public = true)]
5151
public partial struct Player
5252
{
5353
[SpacetimeDB.PrimaryKey]
@@ -193,7 +193,7 @@ spacetimedb.reducer('world', (ctx) => {
193193
```
194194

195195
While SpacetimeDB doesn't support nested transactions,
196-
a reducer can [schedule another reducer](/tables/scheduled-tables) to run at an interval,
196+
a reducer can [schedule another reducer](/tables/schedule-tables) to run at an interval,
197197
or at a specific time.
198198

199199
</TabItem>
@@ -218,7 +218,7 @@ public static void World(ReducerContext ctx)
218218
```
219219

220220
While SpacetimeDB doesn't support nested transactions,
221-
a reducer can [schedule another reducer](/tables/scheduled-tables) to run at an interval,
221+
a reducer can [schedule another reducer](/tables/schedule-tables) to run at an interval,
222222
or at a specific time.
223223

224224
</TabItem>

0 commit comments

Comments
 (0)