-
Notifications
You must be signed in to change notification settings - Fork 0
Adding Features
Recipes for the most common changes. These point at the in-tree how-tos for full step-by-step detail.
docs/adding-a-search-source.md has the full walkthrough. Two paths:
-
Permanent (everyone gets it): edit
src/config/defaults/search-sources.json. Add an object to thesourcesarray with a uniqueid. Done. -
Personal (just you): edit
data/user_config/search-sources.json(gitignored). The schema-aware merge keys onid, so a{"sources":[{"id":"existing-id", "enabled":true}]}override flips just that one field.
Three source types: arxiv (direct arXiv API), arxiv-jr (arXiv filtered by journal_ref), crossref (Crossref by ISSN). Each requires different fields — see the schema at src/config/schema/search-sources.schema.json.
docs/adding-a-config-key.md has the long version. Short version:
- Edit the schema (
src/config/schema/<domain>.schema.json) — type, validation, description. - Edit the defaults (
src/config/defaults/<domain>.json) — sensible default value. - Read it:
getConfig('<domain>').<field>from JS,load_config('<domain>').data['<field>']from Python. - Settings v2 picks up the new field automatically (schema-driven UI).
That's it. No JS service registration, no Python loader update — the manifest already covers all 9 domains.
Same recipe, plus:
- Append the domain name to
MANIFESTin two places:src/config/loader.jsandscq/config/user.py. - (Optional) Add a starter
data/user_config/<domain>.json.exampleso users know the shape. - Settings v2 reads the schema automatically; opt out by removing the entry from
TABSinsrc/ui/settings/main.js.
Services live in src/services/ and are DOM-free. The rule: take state, return data. No document, no window, no addEventListener. Pattern:
// src/services/foo.js
export function fooBar(input) {
// pure logic
}Test it:
// src/tests/services/foo.test.js
import { fooBar } from '../../services/foo.js';
import { describe, it, expect } from 'vitest';
// ...If the service needs the database, import from core/db.js (high-level helpers like run, query) — not from the legacy db_utils.js IIFE.
UI modules live in src/ui/<page>/ and may touch the DOM. Pattern:
// src/ui/database/foo.js
import { fooBar } from '../../services/foo.js';
export function showFooModal() {
// DOM stuff
}Then add it to the page bridge in src/ui/database/main.js:
const BRIDGE = {
// ...existing entries...
showFooModal,
};…and to EXPECTED_BRIDGE_KEYS in src/tests/ui/database/bridge.test.js. The frozen-list spec will fail if you skip this, with a clear "Added without updating list: showFooModal" error.
For scraper modules, the bridge is per-module: append globalThis.showFooModal = showFooModal; at the bottom of the new module file, and add the name to EXPECTED_SCRAPER_BRIDGE in src/tests/ui/scraper/bridge.test.js.
Two patterns depending on whether the underlying module has its own argparse:
Module has its own main(argv): add to _PASSTHROUGH_COMMANDS in scq/cli.py:
"my-cmd": _passthrough_module("scq.my_module", supports_argv=True),Also add to _PASSTHROUGH_MODULES (the docstring-help map). The CLI test's drift catcher fails if you forget either.
Native scq config <subcommand> style: add an add_parser block to _build_parser in scq/cli.py with set_defaults(func=_cmd_my_subcmd), and write the handler.
If you (or a contributor) have an old scraper_config.js with custom sources/presets/tags, run:
scq migrate-from-legacy --dry-run # see what would be written
scq migrate-from-legacy # actually writeRequires Node.js (the legacy file is a JS object literal, not JSON, so we eval it). Writes to data/user_config/search-sources.json and data/user_config/auto-tag-rules.json without overwriting existing files (use --overwrite to replace).
entryTypes and the inline formatBibTeX aren't migrated — those still ship in scraper_config.js itself.
The harness at dev.html is for iterating on individual UI modules. Add a story:
- Edit (or create) a file under
src/dev/stories/. - Each story is
{id, title, description, render(stage, setState)}. - The story's
setStatemirrors current state into the dump pane — useful for watching a form value change.
The harness is hash-routed; reloading after an edit lands you back where you were.
-
A genuinely new architectural seam. Open a plan file in
plans/(gitignored, so it's a personal working artifact) and follow the patterns from the archived ones atplans/archive/architecture-refactor.md. Don't extend the archived plan — start a new file. -
A breaking change to the config schema. Bump the schema's
$idif you must, but prefer additive changes — old user_config files should still load. Schema validation runs every time the loader fires, so a backward-incompatible change is loud at the seam. -
A change that touches both halves. Lock the parity with a vector in
tests/vectors/<category>/. Both runners pick it up automatically. See Architecture for the testing topology.