forked from 1b-agent/opencadstudio-upstream
Add named parameters, UI scale setting, OPTIONS/OP dispatch, and help manual
Named parameters (docs/named_parameters_design.md, stages 3-4): - ParameterTable (src/scene/named_parameters.rs): a document-wide table of named formulas, each resolved through a shared expression evaluator with cross-parameter references, cycle detection, and division-by-zero/parse error reporting. SketchConstraint::driving_param becomes DrivingValue (Literal | Named) instead of a bare f64, resolved at solve time by sketch_solve::build_constraint via Scene::named_parameters. - Persistence (src/scene/named_parameters_persist.rs): same lazy XRecord model sketch_persist.rs already established, synced around open/save. Bumps the sketch-constraint XRecord's own format version (1 -> 2) since DrivingValue's shape doesn't line up with a version-1 blob's bytes. - PARAMETERS command and its editor (src/ui/window/named_parameters.rs): add/remove/edit rows, live validation, duplicate-name detection: Apply defines the table and re-solves every constraint scope with a named-parameter-driven constraint (command_driver.rs). Distance/Angle constraint prompts (modules/draw/constrain/value.rs) now accept either a literal or an existing parameter's name, mirroring how the constraint's own value prompt already worked. - Undo scope-down carried over from this project's own established pattern (constraint-system stage 9): the geometry an Apply moves is one undo step, but the parameter table edit itself has no undo entry yet — a documented follow-up, not a silent gap. UI scale setting (src/app/settings.rs, app/mod.rs, ui/window/options.rs): a whole-interface scale percentage (50-200%), wired directly into iced's own per-window scale_factor hook rather than per-panel font-size plumbing, so it applies immediately to every panel, dialog, and the ribbon at once. Persisted alongside the existing lineweight-display-scale setting. OPTIONS/OP command dispatch (app/commands/view.rs, app/commands/mod.rs): OPTIONS was registered for autocomplete but had no dispatch arm — the Start-tab welcome screen's own button was the only way to reach it. Found live-testing the UI-scale setting, unrelated to that feature itself. "OP" is AutoCAD's own short alias. help/: a new self-contained static user-help manual (manual.md as the authored source, tools/build_help.py splits it into the committed static HTML pages plus search index) — not yet wired into the application. 64 new/updated tests across the named-parameters and constraint-prompt changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e2d39b049d
commit
7cddf31bcb
47 files changed
+4136
-52
No files matched your search
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate local links, anchors, assets, context targets, and basic HTML structure."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from html.parser import HTMLParser
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote
|
||||
|
||||
|
||||
HELP = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class Document(HTMLParser):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.ids: set[str] = set()
|
||||
self.links: list[str] = []
|
||||
self.assets: list[str] = []
|
||||
self.has_main = False
|
||||
self.has_title = False
|
||||
self.images_without_alt = 0
|
||||
|
||||
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
|
||||
values = dict(attrs)
|
||||
if values.get("id"):
|
||||
self.ids.add(values["id"] or "")
|
||||
if tag == "a" and values.get("href"):
|
||||
self.links.append(values["href"] or "")
|
||||
if tag in {"img", "script", "link"}:
|
||||
target = values.get("src") or values.get("href")
|
||||
if target:
|
||||
self.assets.append(target)
|
||||
if tag == "img" and "alt" not in values:
|
||||
self.images_without_alt += 1
|
||||
self.has_main |= tag == "main"
|
||||
self.has_title |= tag == "title"
|
||||
|
||||
|
||||
def load_documents() -> dict[str, Document]:
|
||||
documents: dict[str, Document] = {}
|
||||
for path in sorted(HELP.glob("*.html")):
|
||||
document = Document()
|
||||
document.feed(path.read_text(encoding="utf-8"))
|
||||
documents[path.name] = document
|
||||
return documents
|
||||
|
||||
|
||||
def validate_target(source: str, target: str, documents: dict[str, Document], errors: list[str]) -> None:
|
||||
if target.startswith(("http:", "https:", "mailto:", "javascript:")):
|
||||
return
|
||||
filename, _, fragment = target.partition("#")
|
||||
filename = unquote(filename) or source
|
||||
destination = HELP / filename
|
||||
if not destination.is_file():
|
||||
errors.append(f"{source}: missing target {target}")
|
||||
return
|
||||
if fragment and destination.suffix == ".html":
|
||||
document = documents.get(destination.name)
|
||||
if document is None or fragment not in document.ids:
|
||||
errors.append(f"{source}: missing anchor {target}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
documents = load_documents()
|
||||
errors: list[str] = []
|
||||
if not documents:
|
||||
errors.append("No generated HTML pages found.")
|
||||
|
||||
for name, document in documents.items():
|
||||
if not document.has_main:
|
||||
errors.append(f"{name}: missing <main>")
|
||||
if not document.has_title:
|
||||
errors.append(f"{name}: missing <title>")
|
||||
if document.images_without_alt:
|
||||
errors.append(f"{name}: {document.images_without_alt} image(s) missing alt")
|
||||
for target in document.links + document.assets:
|
||||
validate_target(name, target, documents, errors)
|
||||
|
||||
context = json.loads((HELP / "context-map.json").read_text(encoding="utf-8"))
|
||||
for group in ("commands", "surfaces"):
|
||||
for key, target in context.get(group, {}).items():
|
||||
validate_target(f"context-map.json:{group}.{key}", target, documents, errors)
|
||||
|
||||
if errors:
|
||||
print("Help validation failed:", file=sys.stderr)
|
||||
for error in errors:
|
||||
print(f"- {error}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
links = sum(len(document.links) for document in documents.values())
|
||||
print(f"Validated {len(documents)} pages, {links} links, and {sum(len(context.get(group, {})) for group in ('commands', 'surfaces'))} context targets.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
Reference in new issue
Block a user