release: bump version to 0.43.0

This commit is contained in:
linkong
2026-04-28 16:10:17 +08:00
parent 1cd2dab0ee
commit ac69d5d354
69 changed files with 6954 additions and 1141 deletions

720
rules.md
View File

@@ -1,381 +1,503 @@
# rules.md
---
name: planet-rules
description: Planet repository rules split into LLM-loadable modules.
---
**必须强制执行的约束。违反时立即终止并报错。**
# Planet Rules
**These rules are mandatory. If a requested action conflicts with this file, stop and report the conflict.**
## Loading Protocol
Read this top section first, then load only the modules relevant to the task.
Always load:
- `core`
- `security`
- `workflow`
Load selectively:
| Module | Load when |
|--------|-----------|
| `docs` | Writing, translating, linking, or publishing documentation |
| `uiux` | Visual design, layout, interaction, accessibility, responsive behavior |
| `frontend` | React, TypeScript, CSS, Vite, Bun, admin console, docs UI |
| `backend` | FastAPI, SQLAlchemy, data collectors, database, API performance |
| `earth` | 3D Earth, canvas/Three.js, BGP/vessel/satellite/cable layers, map icons |
| `ai` | AI Provider, LLM gateway, prompts, model config, AI Playground |
| `release` | Version bumps, changelog, version history, commit/tag/push release work |
Do not load the entire file by default for small tasks. Use `rg -n "^## Module:" rules.md` to find module boundaries, then read only the needed block.
---
## Code Style - Imports
## Module: core
### Load When
Always.
### Must
- Keep functions small and focused; one concern per file/module.
- Write self-documenting code; comments explain why, not what.
- Prefer dependency injection for testability.
- Use feature flags for incomplete features.
- Use config files or environment variables for environment-specific settings.
- Maintain one source of truth for business state. Temporary UI state, cached state, and persisted backend state must not become parallel truths.
- Transitional paths are temporary. Once a new implementation is stable, remove old branches, old interfaces, old mocks, and compatibility layers.
- Extract repeated request flow, response handling, auth/header assembly, validation, and state reconciliation into helpers or shared layers.
- Centralize default values, system prompts, placeholder structures, and fixed constants.
- Public interfaces, persisted fields, and state structures must have a current owner and caller. Delete unused ones.
- After large feature work, run an explicit cleanup pass for dead code, duplicated helpers, stale interfaces, and naming drift.
### Code Style
- Python: 4-space indentation, Black style, max line length 100.
- TypeScript: 2-space indentation, Prettier style, max line length 100.
- No trailing whitespace.
- Empty line at end of file.
- Sort imports alphabetically inside groups.
- Never use wildcard imports.
- Avoid unclear abbreviations except common ones such as `id`, `ok`, `err`.
- Prefer descriptive names.
- Keep functions around 50 lines or less where practical.
- Split files before they become mixed-responsibility modules.
### Import Order
Python:
### Python
```python
# Group order: stdlib third-party local
# stdlib -> third-party -> local
import json
from datetime import datetime
from typing import List, Optional
import redis
from fastapi import APIRouter, Depends
from fastapi import APIRouter
from sqlalchemy.orm import Session
from app.core.config import settings
from app.models.user import User
from app.schemas.user import UserCreate
```
### TypeScript
TypeScript:
```typescript
// Group order: React → Third-party → Local
import React, { useState, useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
// React -> third-party -> local
import { useEffect, useState } from 'react'
import { useAuthStore } from '@/stores/auth';
import { api } from '@/services/api';
import axios from 'axios'
import { api } from '@/services/api'
```
**Rules:**
- Sort alphabetically within groups
- Use absolute imports for external packages, relative for local modules
- **NEVER** use wildcard imports (`from module import *`)
### Type Rules
---
- Use type hints throughout Python.
- Define TypeScript interfaces/types for all structured data.
- Avoid `Any`; use specific unions, generics, or `unknown` where appropriate.
- Prefer typed helpers over repeated type casting.
## Code Style - Formatting
### Verify
- **Python:** 4-space indentation, Black formatter, max line 100
- **TypeScript:** 2-space indentation, Prettier, max line 100
- Run formatter **before committing**
- No trailing whitespace
- Empty line at end of file
---
## Code Style - Type Hints
```python
# Use strict typing - NO Any
from typing import List, Dict, Optional, Union
from datetime import datetime
def get_gpu_clusters(
country: Optional[str] = None,
min_gpu_count: int = 0,
) -> List[Dict[str, Union[str, int, float]]]:
...
```
**Rules:**
- Use type hints throughout
- **NEVER** use `Any` - use `unknown` or specific unions
- Define interfaces/types for all data structures
- Generic types preferred over type casting
---
## Code Style - Naming Conventions
| Pattern | Usage | Example |
|---------|-------|---------|
| `camelCase` | Variables, functions, methods | `gpuCluster`, `getData()` |
| `PascalCase` | Classes, components, types | `GPUCluster`, `DataSourceConfig` |
| `SCREAMING_SNAKE_CASE` | Constants, env vars | `API_KEY`, `DATABASE_URL` |
| `kebab-case` | File names, CSS | `data-source-config.css` |
**Rules:**
- Descriptive names - avoid abbreviations except well-known ones (id, ok, err)
- Max function length: 50 lines
- Max file length: 500 lines
---
## Code Style - Error Handling
```python
# Use custom exceptions
class DataSourceError(Exception):
"""Raised when data source fetch fails"""
pass
# Proper error handling with logging
try:
data = await fetch_data(source)
except requests.RequestException as e:
logger.error(f"Failed to fetch from {source}: {e}")
raise DataSourceError(f"Source {source} unavailable") from e
```
**Rules:**
- **NEVER swallow errors silently**
- Use custom exceptions for domain errors
-区分可恢复错误和不可恢复错误
- Log errors with appropriate level (warn/error)
- Include context in all error messages
- Propagate errors to caller unless explicitly handled
---
## Security - NON-NEGOTIABLE
- **NEVER** commit `.env`, secrets, keys, or credentials
- Use environment variables for all credentials
- Validate and sanitize all user inputs
- Use parameterized queries for database operations (SQL injection prevention)
- JWT tokens with short expiration (15 min)
- Redis for token blacklist (logout support)
- Hash passwords with bcrypt/argon2 - **NEVER** store plain text
---
## Git Workflow
- Use deterministic checks before broad manual inspection:
```bash
# Create feature branch
git checkout -b feature/data-collector-huggingface
# Commit message format
git commit -m "feat: add Hugging Face data collector"
git commit -m "fix: resolve WebSocket heartbeat timeout"
git commit -m "docs: update API documentation"
# Before opening PR
git fetch origin && git rebase origin/main
./.venv/bin/python -m pytest -s backend/tests && bun run build
git diff --check
rg -n "TODO|FIXME|console\.log|debugger|print\(" <changed-paths>
```
**Rules:**
- Feature branches from main
- Clear commit messages - "Add user authentication", not "fix"
- **NEVER** force push to main
- Run tests and lint **before** committing
---
## Dependencies
## Module: security
**Rules:**
- Verify package legitimacy before adding
- Prefer well-maintained, widely-used libraries
- Pin dependency versions in `pyproject.toml`, `uv.lock`, and `package.json`
- Review security advisories with `pip-audit` and `bun audit`
- Frontend package management and script execution use **Bun only**
- Frontend commands must use `bun install` / `bun run ...`
- **NEVER** use `npm` / `pnpm` / `yarn` for the frontend project
- **NEVER** add unknown packages
### Load When
---
Always.
## Data Collector Pattern - MANDATORY
### Must
```python
class BaseCollector:
async def fetch(self) -> List[Dict]:
"""Fetch data from source"""
...
- Never commit `.env`, secrets, keys, tokens, or credentials.
- Use environment variables or the configured settings store for credentials.
- Validate and sanitize user input.
- Use parameterized database queries.
- Never store plain-text passwords.
- Hash passwords with bcrypt/argon2.
- Use short-lived JWT tokens when auth tokens are involved.
- Use token blacklist or equivalent revocation support for logout.
- Do not expose full tokens in UI. Show only a short prefix and mask the rest.
def transform(self, raw_data: Dict) -> NormalizedData:
"""Transform to internal format"""
...
### Verify
async def run(self):
"""Full pipeline: fetch -> transform -> save"""
raw = await self.fetch()
data = self.transform(raw)
await self.save(data)
```bash
git diff --name-only HEAD
rg -n "api[_-]?key|client_secret|BEGIN .*PRIVATE KEY|AKIA[0-9A-Z]" .
```
**Rules:**
- Each data source has its own collector class
- Collectors **MUST** inherit from `BaseCollector`
- Implement `fetch()` and `transform()` methods
- Support incremental and full sync modes
---
## WebSocket Communication - MANDATORY
## Module: workflow
```python
# Data frame format
{
"timestamp": "2024-01-15T10:30:00Z",
"type": "update", # or "full"
"payload": {
"gpu_clusters": [...],
"submarine_cables": [...],
"ixp_nodes": [...]
}
}
### Load When
# Heartbeat every 30 seconds
Always.
### Git
- Do not revert user changes unless explicitly requested.
- Do not force push to protected branches.
- Use clear commit messages.
- Run relevant tests and builds before committing.
- Frontend package management must use Bun only.
- Never use `npm`, `pnpm`, or `yarn` in the frontend project.
- Verify package legitimacy before adding dependencies.
- Prefer maintained, widely used libraries.
- Pin dependency versions in `pyproject.toml`, `uv.lock`, and `package.json`.
### Deterministic Context
- Prefer compact CLI evidence over reading large files or full diffs:
```bash
git status --short
git diff --stat HEAD
git diff --name-only HEAD
git diff --unified=0 HEAD -- <path>
rg -n "<pattern>" <path>
```
**Rules:**
- UE5 communicates via WebSocket (not REST)
- Send data frames at configured intervals (default: 5 minutes)
- Include camera position in control frames
- Support auto-cruise and manual interaction modes
---
## Module: docs
### Load When
Writing, translating, linking, restructuring, or publishing docs.
### Must
- Chinese docs under `docs/technical/zh/` must be Chinese prose, not copied English placeholders.
- Keep technical identifiers, API paths, config keys, code symbols, and product names in English where appropriate.
- Explain why a change exists, not only what files changed.
- Prefer updating an existing relevant doc over creating a duplicate.
- Use `##` and `###` headings; avoid going deeper than three levels.
- Use fenced code blocks with language tags.
- Use tables when comparing options or listing parameters.
- Do not reference PR numbers, issue numbers, or the current conversation.
- Internal links inside `docs/technical/zh/` should point to `docs/technical/zh/...` unless intentionally linking to English-only docs.
- Public Docs UI must only expose documents explicitly registered in `frontend/src/pages/Docs/docs-content.ts`.
- Development plans and task notes under `docs/plans/` are not automatically public documentation.
### Required Content
- Background/problem.
- Core design decisions and rationale.
- Key snippets or focused examples.
- Related files and each file's role.
- Operational caveats or verification steps when relevant.
### Verify
```bash
git diff --stat HEAD
git diff --name-only HEAD
ls docs/technical/zh/
rg -n "\]\(([^)]+)\)" docs/technical/zh/<doc>.md
rg -n "/home/ray/dev/linkong/planet/docs/technical/(?!zh|en)" docs/technical/zh --pcre2
```
Check zh/en duplicates:
```bash
python - <<'PY'
from pathlib import Path
same = []
for en in sorted(Path("docs/technical/en").glob("*.md")):
zh = Path("docs/technical/zh") / en.name
if zh.exists() and en.read_text() == zh.read_text():
same.append(en.name)
if same:
raise SystemExit("identical en/zh docs: " + ", ".join(same))
print("no identical en/zh docs")
PY
```
---
## General Guidelines
## Module: uiux
- Keep functions small and focused (single responsibility)
- Write self-documenting code; comment **why**, not what
- One concern per file/module
- Dependency injection for testability
- Feature flags for incomplete features
- Use config files for environment-specific settings
### Load When
## Code Hygiene - MANDATORY
Changing layout, visual hierarchy, controls, interaction states, responsive behavior, or accessibility.
- Maintain a single source of truth for business state. Frontend temporary state, cached state, and persisted backend state must not evolve into parallel truths.
- Transitional paths are temporary. Once a new implementation is stable, remove old branches, old interfaces, old mocks, and compatibility layers instead of letting them linger.
- Repeated logic must be extracted. If request flow, response handling, auth/header assembly, validation, or state reconciliation appears more than once or twice, promote it into a helper or shared layer.
- Repeated backend resource lookup and response assembly must be centralized. Avoid scattering the same `load -> validate -> transform -> respond` pattern across multiple handlers or services.
- Default values, system prompts, placeholder structures, and other fixed constants must be centralized rather than re-declared in multiple states or code paths.
- When debugging layout, scrolling, or overflow issues, first inspect structural ownership of height, width, and overflow before applying isolated style patches.
- Distinct interaction modes must have explicit structure and state semantics. View, edit, loading, error, stopped, and retry states should not be forced through the exact same markup or logic path.
- Presentation state must not pretend to be business state. UI animation, phase labels, and optimistic display layers must defer to real persisted or backend task state when it exists.
- Responsive adaptations must preserve the primary action path. Reflow is fine; losing or displacing the main user action is not.
- After large feature commits, perform an explicit cleanup pass for dead code, temporary branches, duplicated helpers, stale interfaces, and naming drift before considering the work complete.
- If a file or module starts accumulating repeated patterns or mixed responsibilities, stop and refactor before continuing to add more features on top.
- Public interfaces, persisted fields, and state structures must have a current owner and caller. If something is no longer used, delete it instead of keeping it “just in case”.
### Must
- Backend/admin pages are single-screen workspaces first, not long landing pages.
- Common desktop viewports should show the page header, summary/controls, and main work area.
- The main work area gets most available height.
- If text, controls, or tables become unreadable, give that region an internal scrollbar instead of crushing it.
- Overflow ownership must be explicit:
- parent height chain is valid
- height-constrained flex parents use `min-height: 0`
- only the intended scroll node owns `overflow: auto`
- Do not use `overflow: hidden` as a final fix unless another child owns scrolling.
- Tabs define their own scroll strategy; hidden panes must stay hidden.
- Long-form content such as AI briefs, logs, Markdown, raw JSON, and help text should stay readable.
- Prefer stable readable minimum heights plus scrolling for constrained content.
- Avoid brittle `100vh/100vw` in embedded/admin shells; prefer `height: 100%` chains.
- Verify layouts under browser zoom 125% and 150% when changing height-critical screens.
- Avoid wrapper components with implicit layout behavior, such as `Space`, in height-critical scroll regions unless the generated DOM is accounted for.
- Any UI state that hides data or a layer must also reconcile hover, lock, tooltip, and selection state.
### Visual Controls
- Use icons in buttons for common tools/actions when an established icon exists.
- Keep icon-only buttons accessible with `aria-label` and `title`.
- Use segmented controls for modes, switches/checkboxes for binary settings, sliders/inputs for numeric values, menus/selects for option sets, and tabs for views.
- Do not put cards inside cards.
- Do not use visible in-app text to explain obvious UI features or styling.
- Text must fit within its parent on mobile and desktop.
- Do not scale font size with viewport width.
- Letter spacing should usually be `0`.
### Verify
```bash
changed=$(git diff --name-only HEAD -- frontend/src)
[ -z "$changed" ] || rg -n "overflow|min-height|Space|Tabs|aria-label|title=" $changed
```
---
## Query Performance - MANDATORY
## Module: frontend
- **NEVER** load whole tables into Python just to do filtering, pagination, counting, dedupe, or summary aggregation
- Filters, sorting, pagination, `count`, `distinct`, and grouped statistics **MUST** be pushed down to the database whenever the ORM/query builder can express them
- Summary/dashboard endpoints should prefer dedicated aggregate queries or aggregate endpoints, not multiple full-table scans
- For hot paths, avoid selecting large JSON/text payload columns unless the response really needs them
- If an endpoint returns a list, default to database-side pagination instead of `scalars().all()` followed by Python slicing
- When you suspect a query is slow, first check for:
### Load When
Editing React, TypeScript, CSS, Vite, Bun, admin console, public Docs UI, or client-side services.
### Must
- Use Bun for frontend commands:
```bash
bun install
bun run --cwd frontend build
```
- Never use `npm`, `pnpm`, or `yarn`.
- Keep shared behavior in reusable components/services, not page-local copies.
- Prefer existing project components and patterns.
- Keep page state, backend state, and persisted state clearly separated.
- Presentation state must not pretend to be business state.
- Loading, error, stopped, retry, edit, and view states need explicit semantics.
- Responsive adaptations must preserve the primary action path.
- Markdown rendering behavior belongs in the shared Markdown renderer, not individual docs.
- Public Docs navigation must be whitelist-driven through metadata, not file-system fallback.
### TypeScript/CSS
- Define interfaces for API payloads and component props.
- Avoid broad casts.
- Prefer CSS classes over inline styles except for truly dynamic values.
- For fixed-format UI elements, define stable dimensions with `aspect-ratio`, grid tracks, min/max constraints, or container-relative sizing.
### Verify
```bash
bun run --cwd frontend build
git diff --check -- frontend
rg -n "npm|pnpm|yarn" frontend package.json
```
---
## Module: backend
### Load When
Editing FastAPI, SQLAlchemy, collectors, database models, migrations, services, API routes, or performance-sensitive code.
### Must
- Use custom exceptions for domain errors.
- Never swallow errors silently.
- Distinguish recoverable and unrecoverable errors.
- Log errors with useful context and appropriate level.
- Propagate errors unless explicitly handled.
- Repeated `load -> validate -> transform -> respond` flow should be centralized.
- Each data source has its own collector class.
- Collectors must inherit the repository's base collector abstraction when available.
- Collectors implement `fetch()` and `transform()` or the current project-equivalent pipeline hooks.
- Support incremental and full sync modes where the source allows it.
### Query Performance
- Never load whole tables into Python for filtering, pagination, counting, dedupe, or summary aggregation.
- Push filters, sorting, pagination, `count`, `distinct`, and grouped statistics down to the database.
- Summary/dashboard endpoints should prefer aggregate queries or aggregate endpoints.
- Avoid selecting large JSON/text payload columns on hot paths unless needed.
- List endpoints default to database-side pagination.
- When a query is slow, first check:
- full-table ORM loads
- Python-side post-filtering
- repeated summary queries that can be merged
- repeated per-request recomputation that should be cached or aggregated once
- repeated per-request recomputation that should be cached or aggregated
---
### Country Data
## Release Workflow - MANDATORY
- Data sources carrying country, region, or territory fields must validate against `backend/app/core/countries.py`.
- Use `normalize_country(value)` as the single gate.
- If normalization returns `None`, log and reject or flag the value.
- Do not override canonical political labels with raw source labels.
- Add aliases to `COUNTRY_ENTRIES`; do not scatter aliases across collectors or API handlers.
- Frontend country labels should come from the canonical dictionary after normalization.
- When the user asks to `发版`, `bump version`, `release`, or `推送发布类改动`, treat it as a release workflow, not a plain commit
- Apply repository versioning rules consistently:
- `feature` -> `+0.1.0`
- `bugfix` -> `+0.0.1`
- `docs / maintenance / refactor` do **NOT** bump version unless the user explicitly wants a release anyway
- A release bump **MUST** update all version-bearing files together:
- `VERSION`
- `frontend/package.json`
- `pyproject.toml`
- `uv.lock`
- A release bump **MUST** update release records together:
- `docs/CHANGELOG.md`
- `docs/version-history.md`
- Before committing a release, verify the target version appears consistently in all required files
- Before pushing a release, run the smallest relevant validation available for the changed scope and report what was or was not validated
- If runtime output directories are part of the feature flow, confirm they are ignored appropriately so release commits do not accidentally include generated artifacts
- If asked to commit/push release work, do **NOT** skip changelog or version-history updates just because the code changes are small
- Use the repo skill at `/home/ray/dev/linkong/planet/.codex/skills/release-workflow/SKILL.md` whenever performing a release workflow for this repository
---
## Country Data Validation - MANDATORY
- **ALL** data sources that carry a country, region, or territory field (API responses, GeoJSON, CSVs, scraped data, third-party enrichment) **MUST** have their country values validated against the project's canonical country dictionary at `backend/app/core/countries.py` before being stored or displayed
- Use `normalize_country(value)` from `countries.py` as the single gate. If it returns `None`, the value is unrecognized and must be logged and rejected or flagged — **NEVER** silently pass it through
- The dictionary encodes official political positions (e.g., Taiwan → 中国(台湾), Kosovo → 塞尔维亚, Gaza → 巴勒斯坦). Do **NOT** override these with raw source data labels
- When integrating a new data source, run a pre-flight check: extract all distinct country values from the source and verify each one resolves via `normalize_country`. Fix unresolved values before wiring up the collector
- Geographic boundary data (GeoJSON, shapefiles, tilesets) must be post-processed to align feature names and hover labels with the dictionary. The Natural Earth `ne_110m_admin_0_countries` dataset downloaded from GitHub was used as the base for the frontend boundary layer; political corrections were applied manually
- If a new country alias needs to be added to the dictionary, add it to `COUNTRY_ENTRIES` in `countries.py`**NEVER** scatter aliases across individual collectors or API handlers
- Frontend hover tooltips and info cards that display country names must source the name from the canonical dictionary (via `NAME_ZH` after normalization), not raw source strings
---
## Frontend Layout - MANDATORY
- Backend/admin pages must be designed as a `single-screen workspace` first, not as a long vertically stacked document
- In common desktop viewports, users should be able to see:
- page header
- summary/controls
- the main work area
- The main work area must get the majority of the available height; secondary cards must not crowd it out
- If a card or panel would be compressed until text, controls, or tables become unreadable, stop shrinking it and give that region an internal scrollbar instead
- On small screens, high browser zoom, or reduced viewport height, switch to a compact mode or horizontal summary scrolling before allowing important content to be crushed
- Overflow ownership must be explicit:
- parent height chain must be valid
- height-constrained flex parents need `min-height: 0`
- only the intended scroll node should own `overflow: auto`
- Do **NOT** rely on `overflow: hidden` as the final fix for a crowded layout unless another child container is explicitly responsible for scrolling
- For tabs:
- hidden tab panes must stay hidden
- do not override library hidden-pane selectors in a way that makes inactive content visible
- each tab must define its own scroll strategy instead of inheriting a one-size-fits-all table layout
- For long-form content such as AI briefs, logs, markdown, raw JSON, or help text:
- prefer normal document flow inside the content block
- if height is constrained, use a stable minimum readable height plus scrolling
- do not let flex compression collapse the readable area into a thin strip
- Avoid brittle viewport sizing:
- prefer `height: 100%` chains over naive `100vh/100vw` usage in embedded/admin shells
- verify layouts under browser zoom `125%` and `150%`
- Avoid using wrapper components with implicit layout behavior, such as `Space`, for height-critical scroll regions unless their generated DOM is fully accounted for
- Any UI state that hides data or a layer must also reconcile related hover/lock/tooltip/selection state so hidden content is not still “active” in the UI
---
## Icon System - MANDATORY
All canvas-drawn marker icons for the 3D earth visualization **MUST** have a canonical SVG in:
### Verify
```bash
git diff --name-only HEAD -- backend
python3 -m py_compile <changed-python-files>
rg -n "scalars\\(\\)\\.all\\(\\)|\\.all\\(\\).*\\[:|len\\(.*\\.all\\(" backend/app
rg -n "text\\(\"SELECT \\*|execute.*SELECT \\*" backend/app
rg -n "normalize_country|COUNTRY_ENTRIES" backend/app
```
---
## Module: earth
### Load When
Editing `frontend/public/earth`, 3D Earth, canvas/Three.js rendering, BGP/vessel/satellite/cable layers, geographic boundaries, or Earth marker icons.
### Must
- Keep rendering state and UI state explicitly synchronized.
- If a layer is hidden, clear or reconcile related hover, lock, tooltip, and selection state.
- Avoid one-off visual patches before checking render order, coordinate ownership, and data lifecycle.
- Use Three.js for 3D elements.
- Verify 3D/canvas work with real rendering, not only TypeScript build.
- Do not let loading messages, phase labels, or optimistic UI override real backend task state.
- WebSocket data frames should include timestamps, type, and payload when streaming Earth state.
- Default heartbeat for real-time streams is 30 seconds unless a protocol states otherwise.
### Icon System
Canvas-drawn marker icons for Earth must have canonical SVG sources in:
```text
frontend/public/earth/assets/icons/
```
This directory is the **single source of truth** for icon shapes. The canvas/Three.js drawing code may use inline `Path2D` strings or `<canvas>` draw calls derived from these SVGs, but the geometry must originate here.
This directory is the single source of truth for icon geometry. Canvas or Three.js drawing code may use `Path2D` strings or draw calls derived from these SVGs, but the shape must originate here.
### Naming convention
Naming:
`{module}-{description}.svg` in kebab-case.
| Prefix | Context |
|--------|---------|
| `marker-` | Surface map markers |
| `bgp-` | BGP/routing layer icons and event symbols |
| `compute-` | Compute center markers |
| Module prefix | Context |
|---------------|---------|
| `marker-` | Surface map markers (landing points, etc.) |
| `bgp-` | BGP/routing layer icons and event symbols |
| `compute-` | Compute center markers |
Rules:
Examples: `marker-landing-point.svg`, `bgp-event-triangle.svg`, `compute-gpu-cluster.svg`
- Use `fill="currentColor"` for single-color icons.
- Hardcode brand colors only when color is part of icon identity.
- State variants are handled by calling code via color/opacity; do not create separate SVGs per state.
- Use the native canvas coordinate space as `viewBox`, typically `0 0 128 128`.
- When adding an icon, create the SVG, document it in this module, and reference its geometry from rendering code.
### Existing icons
Current icons:
| File | Used in | Description |
|------|---------|-------------|
| `marker-landing-point.svg` | `cables.js` | Cable landing point pin (with circular cutout) |
| `bgp-collector.svg` | `bgp.js` | BGP collector marker (access_point icon + outer ring) |
| `bgp-glow-dot.svg` | `bgp.js` | Base radial glow dot under BGP collector |
| `bgp-event-ring.svg` | `bgp.js` | Ring overlay on event markers |
| `marker-landing-point.svg` | `cables.js` | Cable landing point pin |
| `bgp-collector.svg` | `bgp.js` | BGP collector marker |
| `bgp-glow-dot.svg` | `bgp.js` | Base radial glow dot |
| `bgp-event-ring.svg` | `bgp.js` | Event ring overlay |
| `bgp-event-triangle.svg` | `bgp.js` | Origin anomaly |
| `bgp-event-exclamation.svg` | `bgp.js` | Withdraw event |
| `bgp-event-wave.svg` | `bgp.js` | Flap event |
| `bgp-event-burst.svg` | `bgp.js` | Specific/burst anomaly |
| `bgp-event-leak.svg` | `bgp.js` | Route leak |
| `bgp-event-dot.svg` | `bgp.js` | Generic event |
| `compute-supercomputer.svg` | `compute-centers.js` | Supercomputer (#38bdf8) |
| `compute-gpu-cluster.svg` | `compute-centers.js` | GPU cluster (#2dd4bf) |
| `compute-supercomputer.svg` | `compute-centers.js` | Supercomputer |
| `compute-gpu-cluster.svg` | `compute-centers.js` | GPU cluster |
### Color rules
### Verify
- Use `fill=”currentColor”` for single-color icons so the caller controls the color (event symbols, landing point)
- Hardcode brand colors only when the color is part of the icon identity (compute center types)
- State variants (hover, locked, dimmed) are handled by the calling canvas code via color/opacity — **do not create separate SVG files per state**
```bash
bun run --cwd frontend build
rg -n "hover|locked|selected|tooltip|visible|Path2D|drawImage" frontend/public/earth
ls frontend/public/earth/assets/icons/
```
### Coordinate system
---
- Use the native canvas coordinate space as the `viewBox` (typically `0 0 128 128`)
- Exception: `marker-landing-point.svg` uses a `viewBox` cropped from 1000-unit path space
- SVG must visually match the canvas output at the same scale
## Module: ai
### When adding a new icon
### Load When
1. Create the SVG in `assets/icons/` following naming rules above
2. Add a row to the table in this section
3. Reference the SVG path/geometry in the canvas drawing code — do not invent new shapes directly in JS
Editing AI Provider, LLM gateway, AI Playground, prompt templates, model selection, custom collector mapping generation, or LLM-assisted data transformation.
### Must
- AI provider endpoint/base URL/model/token configuration belongs in settings/integration config, not hardcoded page state.
- The local API route used by the console is not the same as the external LLM provider base URL.
- Common LLM provider presets should be selectable and refreshable from provider docs or catalog logic.
- Store fallback/default provider config centrally.
- Credential previews must reuse the existing product masking convention instead of inventing page-local display logic.
- Never send secrets to logs or docs.
- LLMs may assist with mapping generation or unknown API exploration, but runtime collection should use saved deterministic mapping rules.
- If custom collectors transform into existing domain data, require an explicit target schema.
- If custom collectors introduce entirely new data, do not pretend Earth can use it until a corresponding feature exists.
- Prompts, mapping schemas, default examples, and provider constants must be centralized.
### Verify
```bash
rg -n "AI_PROVIDER|provider_api|base_url|api_key|service_token|prompt|mapping" backend aiprovider frontend/src
git diff --check -- backend aiprovider frontend/src
```
---
## Module: release
### Load When
The user asks to `发版`, bump version, release, commit/push release work, or update changelog/version history as part of a release.
### Must
- Treat release work as release workflow, not a plain commit.
- Use `.codex/skills/release/SKILL.md` when Codex performs a release.
- Version bump rules:
- `feature` -> `+0.1.0`
- `improvement` -> `+0.0.1`
- `bugfix` -> `+0.0.1`
- `docs`, `maintenance`, `refactor` do not bump unless explicitly requested
- Mixed bugfix and small feature/UI work defaults to `improvement` unless the user explicitly chooses another release type.
- A release bump updates all version-bearing files together:
- `VERSION`
- `frontend/package.json`
- `pyproject.toml`
- `uv.lock`
- A release bump updates release records together:
- `docs/CHANGELOG.md`
- `docs/version-history.md`
- `uv.lock` must be regenerated by `uv lock`, never edited manually.
- Before committing a release, verify target version consistency.
- Before pushing a release, run the smallest relevant validation for the changed scope and report what was or was not validated.
- Do not include generated runtime output directories in release commits.
### Verify
```bash
git branch --show-current
git status --short
cat VERSION
rg -n "\"version\":|^version =|version = " frontend/package.json pyproject.toml uv.lock
git diff --stat HEAD
```