diff --git a/.claude/rules/grill-with-docs-for-new-features.md b/.claude/rules/grill-with-docs-for-new-features.md
new file mode 100644
index 00000000..8b9775c9
--- /dev/null
+++ b/.claude/rules/grill-with-docs-for-new-features.md
@@ -0,0 +1,10 @@
+# Use /grill-with-docs For Development
+
+When a user asks to develop this codebase, use the `/grill-with-docs` skill before implementation.
+
+## Required behavior
+
+- Start a grilling session to resolve terminology, scope, and trade-offs.
+- Ask one question at a time and wait for user feedback before continuing.
+- If a question can be answered from the codebase or docs, check there first.
+- Proceed to implementation only after the development intent is clear.
diff --git a/.claude/rules/keep-system-architecture-up-to-date.md b/.claude/rules/keep-system-architecture-up-to-date.md
new file mode 100644
index 00000000..4244659f
--- /dev/null
+++ b/.claude/rules/keep-system-architecture-up-to-date.md
@@ -0,0 +1,18 @@
+# Keep System Architecture Doc Up To Date
+
+## Policy
+
+`system-architecture.md` is the source of truth for the repository architecture.
+
+## Required behavior
+
+When a task changes the system architecture (components, boundaries, data flow, key abstractions, integrations, or deployment topology), update `system-architecture.md` in the same task.
+
+If no architecture impact exists, explicitly confirm that no update is needed.
+
+## Verification checklist
+
+- Architecture-impacting code changes include corresponding `system-architecture.md` updates.
+- New or changed architectural terms are reflected consistently.
+- Mermaid diagrams remain accurate when affected.
+- No secrets are introduced in documentation.
diff --git a/.claude/rules/mirror-providers.md b/.claude/rules/mirror-providers.md
new file mode 100644
index 00000000..a289117e
--- /dev/null
+++ b/.claude/rules/mirror-providers.md
@@ -0,0 +1,23 @@
+# Mirror Providers Rule
+
+## Policy
+
+Keep both provider folders aligned:
+
+- skills are mirrored between `.claude/skills/` and `.cursor/skills/`
+- rules are mirrored between `.claude/rules/` and `.cursor/rules/`
+
+## Required behavior
+
+When creating, updating, renaming, or deleting a skill or rule in one provider folder, apply the equivalent change to the other provider folder in the same task.
+
+## Allowed differences
+
+Only vendor-required differences are allowed (format or loader specifics). Core guidance and intent must remain equivalent.
+
+## Verification checklist
+
+- Both provider folders contain equivalent skill directories and rule files.
+- Names map consistently across providers.
+- Referenced local docs/scripts resolve in each provider folder.
+- No secrets are introduced.
diff --git a/.claude/skills/grill-with-docs/ADR-FORMAT.md b/.claude/skills/grill-with-docs/ADR-FORMAT.md
new file mode 100644
index 00000000..da7e78ec
--- /dev/null
+++ b/.claude/skills/grill-with-docs/ADR-FORMAT.md
@@ -0,0 +1,47 @@
+# ADR Format
+
+ADRs live in `docs/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc.
+
+Create the `docs/adr/` directory lazily — only when the first ADR is needed.
+
+## Template
+
+```md
+# {Short title of the decision}
+
+{1-3 sentences: what's the context, what did we decide, and why.}
+```
+
+That's it. An ADR can be a single paragraph. The value is in recording *that* a decision was made and *why* — not in filling out sections.
+
+## Optional sections
+
+Only include these when they add genuine value. Most ADRs won't need them.
+
+- **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`) — useful when decisions are revisited
+- **Considered Options** — only when the rejected alternatives are worth remembering
+- **Consequences** — only when non-obvious downstream effects need to be called out
+
+## Numbering
+
+Scan `docs/adr/` for the highest existing number and increment by one.
+
+## When to offer an ADR
+
+All three of these must be true:
+
+1. **Hard to reverse** — the cost of changing your mind later is meaningful
+2. **Surprising without context** — a future reader will look at the code and wonder "why on earth did they do it this way?"
+3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons
+
+If a decision is easy to reverse, skip it — you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing."
+
+### What qualifies
+
+- **Architectural shape.** "We're using a monorepo." "The write model is event-sourced, the read model is projected into Postgres."
+- **Integration patterns between contexts.** "Ordering and Billing communicate via domain events, not synchronous HTTP."
+- **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library — just the ones that would take a quarter to swap out.
+- **Boundary and scope decisions.** "Customer data is owned by the Customer context; other contexts reference it by ID only." The explicit no-s are as valuable as the yes-s.
+- **Deliberate deviations from the obvious path.** "We're using manual SQL instead of an ORM because X." Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate.
+- **Constraints not visible in the code.** "We can't use AWS because of compliance requirements." "Response times must be under 200ms because of the partner API contract."
+- **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it — otherwise someone will suggest GraphQL again in six months.
diff --git a/.claude/skills/grill-with-docs/CONTEXT-FORMAT.md b/.claude/skills/grill-with-docs/CONTEXT-FORMAT.md
new file mode 100644
index 00000000..ddfa247c
--- /dev/null
+++ b/.claude/skills/grill-with-docs/CONTEXT-FORMAT.md
@@ -0,0 +1,77 @@
+# CONTEXT.md Format
+
+## Structure
+
+```md
+# {Context Name}
+
+{One or two sentence description of what this context is and why it exists.}
+
+## Language
+
+**Order**:
+{A concise description of the term}
+_Avoid_: Purchase, transaction
+
+**Invoice**:
+A request for payment sent to a customer after delivery.
+_Avoid_: Bill, payment request
+
+**Customer**:
+A person or organization that places orders.
+_Avoid_: Client, buyer, account
+
+## Relationships
+
+- An **Order** produces one or more **Invoices**
+- An **Invoice** belongs to exactly one **Customer**
+
+## Example dialogue
+
+> **Dev:** "When a **Customer** places an **Order**, do we create the **Invoice** immediately?"
+> **Domain expert:** "No — an **Invoice** is only generated once a **Fulfillment** is confirmed."
+
+## Flagged ambiguities
+
+- "account" was used to mean both **Customer** and **User** — resolved: these are distinct concepts.
+```
+
+## Rules
+
+- **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others as aliases to avoid.
+- **Flag conflicts explicitly.** If a term is used ambiguously, call it out in "Flagged ambiguities" with a clear resolution.
+- **Keep definitions tight.** One sentence max. Define what it IS, not what it does.
+- **Show relationships.** Use bold term names and express cardinality where obvious.
+- **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs.
+- **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine.
+- **Write an example dialogue.** A conversation between a dev and a domain expert that demonstrates how the terms interact naturally and clarifies boundaries between related concepts.
+
+## Single vs multi-context repos
+
+**Single context (most repos):** One `CONTEXT.md` at the repo root.
+
+**Multiple contexts:** A `CONTEXT-MAP.md` at the repo root lists the contexts, where they live, and how they relate to each other:
+
+```md
+# Context Map
+
+## Contexts
+
+- [Ordering](./src/ordering/CONTEXT.md) — receives and tracks customer orders
+- [Billing](./src/billing/CONTEXT.md) — generates invoices and processes payments
+- [Fulfillment](./src/fulfillment/CONTEXT.md) — manages warehouse picking and shipping
+
+## Relationships
+
+- **Ordering → Fulfillment**: Ordering emits `OrderPlaced` events; Fulfillment consumes them to start picking
+- **Fulfillment → Billing**: Fulfillment emits `ShipmentDispatched` events; Billing consumes them to generate invoices
+- **Ordering ↔ Billing**: Shared types for `CustomerId` and `Money`
+```
+
+The skill infers which structure applies:
+
+- If `CONTEXT-MAP.md` exists, read it to find contexts
+- If only a root `CONTEXT.md` exists, single context
+- If neither exists, create a root `CONTEXT.md` lazily when the first term is resolved
+
+When multiple contexts exist, infer which one the current topic relates to. If unclear, ask.
diff --git a/.claude/skills/grill-with-docs/SKILL.md b/.claude/skills/grill-with-docs/SKILL.md
new file mode 100644
index 00000000..5ea0aa91
--- /dev/null
+++ b/.claude/skills/grill-with-docs/SKILL.md
@@ -0,0 +1,88 @@
+---
+name: grill-with-docs
+description: Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when user wants to stress-test a plan against their project's language and documented decisions.
+---
+
+
+
+Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.
+
+Ask the questions one at a time, waiting for feedback on each question before continuing.
+
+If a question can be answered by exploring the codebase, explore the codebase instead.
+
+
+
+
+
+## Domain awareness
+
+During codebase exploration, also look for existing documentation:
+
+### File structure
+
+Most repos have a single context:
+
+```
+/
+├── CONTEXT.md
+├── docs/
+│ └── adr/
+│ ├── 0001-event-sourced-orders.md
+│ └── 0002-postgres-for-write-model.md
+└── src/
+```
+
+If a `CONTEXT-MAP.md` exists at the root, the repo has multiple contexts. The map points to where each one lives:
+
+```
+/
+├── CONTEXT-MAP.md
+├── docs/
+│ └── adr/ ← system-wide decisions
+├── src/
+│ ├── ordering/
+│ │ ├── CONTEXT.md
+│ │ └── docs/adr/ ← context-specific decisions
+│ └── billing/
+│ ├── CONTEXT.md
+│ └── docs/adr/
+```
+
+Create files lazily — only when you have something to write. If no `CONTEXT.md` exists, create one when the first term is resolved. If no `docs/adr/` exists, create it when the first ADR is needed.
+
+## During the session
+
+### Challenge against the glossary
+
+When the user uses a term that conflicts with the existing language in `CONTEXT.md`, call it out immediately. "Your glossary defines 'cancellation' as X, but you seem to mean Y — which is it?"
+
+### Sharpen fuzzy language
+
+When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account' — do you mean the Customer or the User? Those are different things."
+
+### Discuss concrete scenarios
+
+When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts.
+
+### Cross-reference with code
+
+When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible — which is right?"
+
+### Update CONTEXT.md inline
+
+When a term is resolved, update `CONTEXT.md` right there. Don't batch these up — capture them as they happen. Use the format in [CONTEXT-FORMAT.md](./CONTEXT-FORMAT.md).
+
+`CONTEXT.md` should be totally devoid of implementation details. Do not treat `CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else.
+
+### Offer ADRs sparingly
+
+Only offer to create an ADR when all three are true:
+
+1. **Hard to reverse** — the cost of changing your mind later is meaningful
+2. **Surprising without context** — a future reader will wonder "why did they do it this way?"
+3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons
+
+If any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md).
+
+
diff --git a/.cursor/rules/grill-with-docs-for-new-features.mdc b/.cursor/rules/grill-with-docs-for-new-features.mdc
new file mode 100644
index 00000000..c6d92e36
--- /dev/null
+++ b/.cursor/rules/grill-with-docs-for-new-features.mdc
@@ -0,0 +1,15 @@
+---
+description: Use grill-with-docs for development work
+alwaysApply: true
+---
+
+# Use /grill-with-docs For Development
+
+When a user asks to develop this codebase, use the `/grill-with-docs` skill before implementation.
+
+## Required behavior
+
+- Start a grilling session to resolve terminology, scope, and trade-offs.
+- Ask one question at a time and wait for user feedback before continuing.
+- If a question can be answered from the codebase or docs, check there first.
+- Proceed to implementation only after the development intent is clear.
diff --git a/.cursor/rules/keep-system-architecture-up-to-date.mdc b/.cursor/rules/keep-system-architecture-up-to-date.mdc
new file mode 100644
index 00000000..4244659f
--- /dev/null
+++ b/.cursor/rules/keep-system-architecture-up-to-date.mdc
@@ -0,0 +1,18 @@
+# Keep System Architecture Doc Up To Date
+
+## Policy
+
+`system-architecture.md` is the source of truth for the repository architecture.
+
+## Required behavior
+
+When a task changes the system architecture (components, boundaries, data flow, key abstractions, integrations, or deployment topology), update `system-architecture.md` in the same task.
+
+If no architecture impact exists, explicitly confirm that no update is needed.
+
+## Verification checklist
+
+- Architecture-impacting code changes include corresponding `system-architecture.md` updates.
+- New or changed architectural terms are reflected consistently.
+- Mermaid diagrams remain accurate when affected.
+- No secrets are introduced in documentation.
diff --git a/.cursor/rules/mirror-providers.mdc b/.cursor/rules/mirror-providers.mdc
new file mode 100644
index 00000000..3b67f533
--- /dev/null
+++ b/.cursor/rules/mirror-providers.mdc
@@ -0,0 +1,23 @@
+# Mirror Providers Rule
+
+## Policy
+
+Keep both provider folders aligned:
+
+- skills are mirrored between `.cursor/skills/` and `.claude/skills/`
+- rules are mirrored between `.cursor/rules/` and `.claude/rules/`
+
+## Required behavior
+
+When creating, updating, renaming, or deleting a skill or rule in one provider folder, apply the equivalent change to the other provider folder in the same task.
+
+## Allowed differences
+
+Only vendor-required differences are allowed (format or loader specifics). Core guidance and intent must remain equivalent.
+
+## Verification checklist
+
+- Both provider folders contain equivalent skill directories and rule files.
+- Names map consistently across providers.
+- Referenced local docs/scripts resolve in each provider folder.
+- No secrets are introduced.
diff --git a/.cursor/skills/grill-with-docs/ADR-FORMAT.md b/.cursor/skills/grill-with-docs/ADR-FORMAT.md
new file mode 100644
index 00000000..da7e78ec
--- /dev/null
+++ b/.cursor/skills/grill-with-docs/ADR-FORMAT.md
@@ -0,0 +1,47 @@
+# ADR Format
+
+ADRs live in `docs/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc.
+
+Create the `docs/adr/` directory lazily — only when the first ADR is needed.
+
+## Template
+
+```md
+# {Short title of the decision}
+
+{1-3 sentences: what's the context, what did we decide, and why.}
+```
+
+That's it. An ADR can be a single paragraph. The value is in recording *that* a decision was made and *why* — not in filling out sections.
+
+## Optional sections
+
+Only include these when they add genuine value. Most ADRs won't need them.
+
+- **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`) — useful when decisions are revisited
+- **Considered Options** — only when the rejected alternatives are worth remembering
+- **Consequences** — only when non-obvious downstream effects need to be called out
+
+## Numbering
+
+Scan `docs/adr/` for the highest existing number and increment by one.
+
+## When to offer an ADR
+
+All three of these must be true:
+
+1. **Hard to reverse** — the cost of changing your mind later is meaningful
+2. **Surprising without context** — a future reader will look at the code and wonder "why on earth did they do it this way?"
+3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons
+
+If a decision is easy to reverse, skip it — you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing."
+
+### What qualifies
+
+- **Architectural shape.** "We're using a monorepo." "The write model is event-sourced, the read model is projected into Postgres."
+- **Integration patterns between contexts.** "Ordering and Billing communicate via domain events, not synchronous HTTP."
+- **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library — just the ones that would take a quarter to swap out.
+- **Boundary and scope decisions.** "Customer data is owned by the Customer context; other contexts reference it by ID only." The explicit no-s are as valuable as the yes-s.
+- **Deliberate deviations from the obvious path.** "We're using manual SQL instead of an ORM because X." Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate.
+- **Constraints not visible in the code.** "We can't use AWS because of compliance requirements." "Response times must be under 200ms because of the partner API contract."
+- **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it — otherwise someone will suggest GraphQL again in six months.
diff --git a/.cursor/skills/grill-with-docs/CONTEXT-FORMAT.md b/.cursor/skills/grill-with-docs/CONTEXT-FORMAT.md
new file mode 100644
index 00000000..ddfa247c
--- /dev/null
+++ b/.cursor/skills/grill-with-docs/CONTEXT-FORMAT.md
@@ -0,0 +1,77 @@
+# CONTEXT.md Format
+
+## Structure
+
+```md
+# {Context Name}
+
+{One or two sentence description of what this context is and why it exists.}
+
+## Language
+
+**Order**:
+{A concise description of the term}
+_Avoid_: Purchase, transaction
+
+**Invoice**:
+A request for payment sent to a customer after delivery.
+_Avoid_: Bill, payment request
+
+**Customer**:
+A person or organization that places orders.
+_Avoid_: Client, buyer, account
+
+## Relationships
+
+- An **Order** produces one or more **Invoices**
+- An **Invoice** belongs to exactly one **Customer**
+
+## Example dialogue
+
+> **Dev:** "When a **Customer** places an **Order**, do we create the **Invoice** immediately?"
+> **Domain expert:** "No — an **Invoice** is only generated once a **Fulfillment** is confirmed."
+
+## Flagged ambiguities
+
+- "account" was used to mean both **Customer** and **User** — resolved: these are distinct concepts.
+```
+
+## Rules
+
+- **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others as aliases to avoid.
+- **Flag conflicts explicitly.** If a term is used ambiguously, call it out in "Flagged ambiguities" with a clear resolution.
+- **Keep definitions tight.** One sentence max. Define what it IS, not what it does.
+- **Show relationships.** Use bold term names and express cardinality where obvious.
+- **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs.
+- **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine.
+- **Write an example dialogue.** A conversation between a dev and a domain expert that demonstrates how the terms interact naturally and clarifies boundaries between related concepts.
+
+## Single vs multi-context repos
+
+**Single context (most repos):** One `CONTEXT.md` at the repo root.
+
+**Multiple contexts:** A `CONTEXT-MAP.md` at the repo root lists the contexts, where they live, and how they relate to each other:
+
+```md
+# Context Map
+
+## Contexts
+
+- [Ordering](./src/ordering/CONTEXT.md) — receives and tracks customer orders
+- [Billing](./src/billing/CONTEXT.md) — generates invoices and processes payments
+- [Fulfillment](./src/fulfillment/CONTEXT.md) — manages warehouse picking and shipping
+
+## Relationships
+
+- **Ordering → Fulfillment**: Ordering emits `OrderPlaced` events; Fulfillment consumes them to start picking
+- **Fulfillment → Billing**: Fulfillment emits `ShipmentDispatched` events; Billing consumes them to generate invoices
+- **Ordering ↔ Billing**: Shared types for `CustomerId` and `Money`
+```
+
+The skill infers which structure applies:
+
+- If `CONTEXT-MAP.md` exists, read it to find contexts
+- If only a root `CONTEXT.md` exists, single context
+- If neither exists, create a root `CONTEXT.md` lazily when the first term is resolved
+
+When multiple contexts exist, infer which one the current topic relates to. If unclear, ask.
diff --git a/.cursor/skills/grill-with-docs/SKILL.md b/.cursor/skills/grill-with-docs/SKILL.md
new file mode 100644
index 00000000..5ea0aa91
--- /dev/null
+++ b/.cursor/skills/grill-with-docs/SKILL.md
@@ -0,0 +1,88 @@
+---
+name: grill-with-docs
+description: Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when user wants to stress-test a plan against their project's language and documented decisions.
+---
+
+
+
+Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.
+
+Ask the questions one at a time, waiting for feedback on each question before continuing.
+
+If a question can be answered by exploring the codebase, explore the codebase instead.
+
+
+
+
+
+## Domain awareness
+
+During codebase exploration, also look for existing documentation:
+
+### File structure
+
+Most repos have a single context:
+
+```
+/
+├── CONTEXT.md
+├── docs/
+│ └── adr/
+│ ├── 0001-event-sourced-orders.md
+│ └── 0002-postgres-for-write-model.md
+└── src/
+```
+
+If a `CONTEXT-MAP.md` exists at the root, the repo has multiple contexts. The map points to where each one lives:
+
+```
+/
+├── CONTEXT-MAP.md
+├── docs/
+│ └── adr/ ← system-wide decisions
+├── src/
+│ ├── ordering/
+│ │ ├── CONTEXT.md
+│ │ └── docs/adr/ ← context-specific decisions
+│ └── billing/
+│ ├── CONTEXT.md
+│ └── docs/adr/
+```
+
+Create files lazily — only when you have something to write. If no `CONTEXT.md` exists, create one when the first term is resolved. If no `docs/adr/` exists, create it when the first ADR is needed.
+
+## During the session
+
+### Challenge against the glossary
+
+When the user uses a term that conflicts with the existing language in `CONTEXT.md`, call it out immediately. "Your glossary defines 'cancellation' as X, but you seem to mean Y — which is it?"
+
+### Sharpen fuzzy language
+
+When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account' — do you mean the Customer or the User? Those are different things."
+
+### Discuss concrete scenarios
+
+When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts.
+
+### Cross-reference with code
+
+When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible — which is right?"
+
+### Update CONTEXT.md inline
+
+When a term is resolved, update `CONTEXT.md` right there. Don't batch these up — capture them as they happen. Use the format in [CONTEXT-FORMAT.md](./CONTEXT-FORMAT.md).
+
+`CONTEXT.md` should be totally devoid of implementation details. Do not treat `CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else.
+
+### Offer ADRs sparingly
+
+Only offer to create an ADR when all three are true:
+
+1. **Hard to reverse** — the cost of changing your mind later is meaningful
+2. **Surprising without context** — a future reader will wonder "why did they do it this way?"
+3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons
+
+If any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md).
+
+
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 011ff6df..f053f4d0 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,10 +1,9 @@
---
name: Bug report
about: Create a report to help us improve
-title: ''
+title: ""
labels: bug
-assignees: ''
-
+assignees: ""
---
@@ -14,6 +13,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
+
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@@ -26,9 +26,10 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- - OS: [e.g. iOS]
- - Browser [e.g. chrome, safari]
- - Version [e.g. 22]
+
+- OS: [e.g. iOS]
+- Browser [e.g. chrome, safari]
+- Version [e.g. 22]
- PaperMemory version [e.g. 0.3.3]
**Idea for a fix** (Can you think of a solution? Can you contribute a PR if you do?)
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 59094e26..b97aa9ad 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,10 +1,9 @@
---
name: Feature request
about: Suggest an idea for this project
-title: ''
+title: ""
labels: enhancement
-assignees: ''
-
+assignees: ""
---
**Is your feature request related to a problem? Please describe.**
diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md
index 03f75317..00f59ac6 100644
--- a/.github/ISSUE_TEMPLATE/other.md
+++ b/.github/ISSUE_TEMPLATE/other.md
@@ -1,10 +1,7 @@
---
name: Other
about: Something else to ask/suggest/flag?
-title: ''
-labels: ''
-assignees: ''
-
+title: ""
+labels: ""
+assignees: ""
---
-
-
diff --git a/.github/workflows/_build.yml b/.github/workflows/_build.yml
new file mode 100644
index 00000000..82bf13ce
--- /dev/null
+++ b/.github/workflows/_build.yml
@@ -0,0 +1,24 @@
+name: Build (reusable)
+
+on:
+ workflow_call:
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "22.x"
+ cache: "npm"
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build Chrome & Firefox
+ run: npm run build
diff --git a/.github/workflows/_test-matrix.yml b/.github/workflows/_test-matrix.yml
new file mode 100644
index 00000000..8dfc8609
--- /dev/null
+++ b/.github/workflows/_test-matrix.yml
@@ -0,0 +1,59 @@
+name: Test matrix (reusable)
+
+on:
+ workflow_call:
+ secrets:
+ VICT0RSCH_GITHUB_PAT:
+ required: true
+
+permissions:
+ contents: read
+
+jobs:
+ test-matrix:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - name: Utils Tests
+ files: test/test-utils.js
+ - name: Extension Loading
+ files: test/test-extension-loading.js
+ - name: Popup UI
+ files: test/test-popup-search.js test/test-popup-paper-ui.js
+ - name: Memory UI
+ files: test/test-memory-item-actions.js test/test-memory-table-ui.js
+ - name: Menu
+ files: test/test-menu.js
+ - name: Sync
+ files: test/test-sync.js
+ - name: Duplicates
+ files: test/test-duplicates.js
+ - name: All test scripts are listed in a workflow
+ files: test/test-meta.js
+
+ name: ${{ matrix.name }}
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "22.x"
+ cache: "npm"
+
+ - name: Install Dependencies
+ run: npm ci
+
+ - name: Build Extension
+ run: npm run build:chrome
+
+ - uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
+ name: Run Test
+ env:
+ github_pat: ${{ secrets.VICT0RSCH_GITHUB_PAT }}
+ with:
+ timeout_minutes: 20
+ max_attempts: 2
+ retry_on: error
+ command: npm run test:file ${{ matrix.files }}
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a0723649..f15dfd9e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,18 +1,9 @@
-name: automerge
+name: Build CI
on: [push, pull_request]
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-node@v4
- with:
- node-version: "22.x"
+permissions:
+ contents: read
- - name: Install Yarn
- run: npm install --global yarn
- - name: Install dependencies
- run: yarn install
- - name: Build
- run: gulp build
+jobs:
+ build:
+ uses: ./.github/workflows/_build.yml
diff --git a/.github/workflows/ci.yaml b/.github/workflows/docs.yaml
similarity index 98%
rename from .github/workflows/ci.yaml
rename to .github/workflows/docs.yaml
index bf1e63d5..43773f0b 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/docs.yaml
@@ -1,4 +1,4 @@
-name: ci
+name: Docs CI
on:
push:
branches:
diff --git a/.github/workflows/submit.yml b/.github/workflows/submit.yml
index 8a693282..8ced52bb 100644
--- a/.github/workflows/submit.yml
+++ b/.github/workflows/submit.yml
@@ -1,23 +1,66 @@
-name: Submit
+name: Submit to stores
on:
- workflow_dispatch:
- inputs:
- tag:
- description: "Release tag to submit, i.e 0.4.3"
- required: true
+ workflow_dispatch:
+ inputs:
+ target:
+ description: "Which store(s) to submit to"
+ type: choice
+ options: [both, chrome, firefox]
+ default: both
+ dry_run:
+ description: "Dry run (validate credentials without submitting)"
+ type: boolean
+ default: true
+
+concurrency:
+ group: submit-stores
+ cancel-in-progress: false
+
+permissions:
+ contents: read
jobs:
- submit:
- runs-on: ubuntu-latest
- steps:
- - name: Download Github Release Assets
- uses: plasmo-corp/download-release-asset@v1.0.0
- with:
- files: Archive-*
- tag: ${{ github.event.inputs.tag }}
- - name: Browser Plugin Publish
- uses: plasmo-corp/bpp@v1
- with:
- artifact: "Archive-${{ github.event.inputs.tag }}.zip"
- keys: ${{ secrets.SUBMIT_KEYS }}
+ test-matrix:
+ uses: ./.github/workflows/_test-matrix.yml
+ secrets:
+ VICT0RSCH_GITHUB_PAT: ${{ secrets.VICT0RSCH_GITHUB_PAT }}
+
+ submit:
+ needs: test-matrix
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "22.x"
+ cache: "npm"
+
+ - run: npm ci
+
+ - run: npm run zip
+
+ - name: Submit to Chrome Web Store
+ if: github.event.inputs.target == 'both' || github.event.inputs.target == 'chrome'
+ run: |
+ npx wxt submit \
+ ${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }} \
+ --chrome-zip "dist/*-chrome.zip"
+ env:
+ CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
+ CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
+ CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
+ CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
+
+ - name: Submit to Firefox Add-ons
+ if: github.event.inputs.target == 'both' || github.event.inputs.target == 'firefox'
+ run: |
+ npx wxt submit \
+ ${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }} \
+ --firefox-zip "dist/*-firefox.zip" \
+ --firefox-sources-zip "dist/*-sources.zip"
+ env:
+ FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }}
+ FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }}
+ FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..2d2c8238
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,45 @@
+name: Tests CI
+on: push
+
+permissions:
+ contents: read
+
+jobs:
+ test-matrix:
+ uses: ./.github/workflows/_test-matrix.yml
+ secrets:
+ VICT0RSCH_GITHUB_PAT: ${{ secrets.VICT0RSCH_GITHUB_PAT }}
+
+ test-storage:
+ runs-on: ubuntu-latest
+ name: Storage Tests
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "22.x"
+ cache: "npm"
+
+ - name: Install Dependencies
+ run: npm ci
+
+ - name: Build Extension
+ run: npm run build:chrome
+
+ - uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
+ name: Run Storage Test
+ with:
+ timeout_minutes: 20
+ max_attempts: 2
+ retry_on: error
+ command: npm run test:file test/test-storage.js
+
+ - uses: actions/upload-artifact@v4
+ if: always() # Upload screenshots even on failure for debugging
+ with:
+ name: test-storage-screenshots
+ path: ./tmp/*.jpg
+ if-no-files-found: ignore
+ retention-days: 7
+ compression-level: 3
diff --git a/.gitignore b/.gitignore
index b9ecc1ee..e07b7252 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,9 +4,17 @@
extra/archives/
node_modules/
keys.json
+.env
+.env.submit
+.env.*
test/tmp/
.nyc_output/
coverage
.cache
.todo
site/
+__pycache__
+tmp/
+dist/
+.wxt/
+*.log
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..ee0caf5e
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,6 @@
+*.min.*
+**/dist/
+**/build/
+**/bundle/
+**/bundles/
+**.bundle.**
diff --git a/.prettierrc b/.prettierrc
index 998b722a..c4d3a39e 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,5 +1,12 @@
{
- "tabWidth": 4,
- "useTabs": false,
- "printWidth": 88
-}
\ No newline at end of file
+ "tabWidth": 4,
+ "printWidth": 88,
+ "overrides": [
+ {
+ "files": ["*.yaml", "*.yml"],
+ "options": {
+ "tabWidth": 2
+ }
+ }
+ ]
+}
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 00000000..fba70d90
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,83 @@
+# 1. Think Before Coding
+
+**Don't assume. Don't hide confusion. Surface tradeoffs.**
+
+Before implementing:
+- State your assumptions explicitly. If uncertain, ask.
+- If multiple interpretations exist, present them - don't pick silently.
+- If a simpler approach exists, say so. Push back when warranted.
+- If something is unclear, stop. Name what's confusing. Ask.
+- Read `system-architecture.md` first to understand repository structure and boundaries before broad grepping/searching across the entire codebase.
+- If the user's request is too vague or short or unclear, use the "grill-with-docs" skill to clarify the request.
+- Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
+
+# 2. Simplicity First
+
+**Minimum code that solves the problem. Nothing speculative.**
+
+- No features beyond what was asked.
+- No abstractions for single-use code.
+- No "flexibility" or "configurability" that wasn't requested.
+- No error handling for impossible scenarios.
+- If you write 200 lines and it could be 50, rewrite it.
+
+Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
+
+# 3. Surgical Changes
+
+**Touch only what you must. Clean up only your own mess.**
+
+When editing existing code:
+- Don't "improve" adjacent code, comments, or formatting.
+- Don't refactor things that aren't broken.
+- Match existing style, even if you'd do it differently.
+- If you notice unrelated dead code, mention it - don't delete it.
+
+When your changes create orphans:
+- Remove imports/variables/functions that YOUR changes made unused.
+- Don't remove pre-existing dead code unless asked.
+
+The test: Every changed line should trace directly to the user's request.
+
+# 4. Plans Must Be Executable By Small Models
+
+**Every plan must be clear, exhaustive, imperative, and verifiable.**
+
+When writing a plan:
+- Use numbered imperative steps (`Do X`, `Then do Y`), never vague goals.
+- Make the plan exhaustive for the requested scope: no hidden steps, no implicit work.
+- State assumptions and prerequisites explicitly before execution steps.
+- For each step, define an objective verification check (test, command, observable output, or diff condition).
+- Define concrete completion criteria so an agent can truthfully decide "done" vs "not done."
+- Use language that a small model can follow literally without guessing intent.
+
+Hard rule:
+- If a small-capability agent could not implement the plan truthfully from the plan text alone, the plan is invalid and must be rewritten.
+
+# 5. Agent Entry Point (Shared Across Providers)
+
+Provider-specific rule files are the source of truth for cross-provider mirroring:
+
+- `.cursor/rules/mirror-providers.mdc`
+- `.claude/rules/mirror-providers.md`
+
+# 6. Be safe, be consistent
+
+**Security is a hard gate. A task is not done until this section is satisfied.**
+
+Always perform a security pass while reading and writing code:
+- Treat secrets exposure as critical (API keys, tokens, passwords, credentials, private keys, webhook URLs, `.env` contents).
+- Never hardcode secrets in code, tests, docs, examples, commits, PR text, logs, or terminal snippets.
+- Do not move secrets into "safe-looking" files (fixtures, sample configs, scripts). If a value is sensitive, keep it out.
+- If you detect a leak or high-risk pattern, stop and warn clearly. Do this even when it is outside the original request.
+
+Before declaring completion, run a critical consistency review of your own work:
+- Verify the result is consistent with sections 1-4: explicit assumptions, minimal scope, and surgical edits only.
+- Confirm every changed line traces directly to the user's request (no speculative features, no side-quest refactors).
+- Check that your naming, structure, and behavior align with existing project language and conventions.
+- Check consistency with documentation produced by the `/grill-with-docs` process (`CONTEXT.md`, `CONTEXT-MAP.md`, and relevant ADRs). If code and docs disagree, flag it explicitly.
+- Challenge your own decisions: "Where did I overreach?" and "What part is inconsistent with the stated principles?"
+- If inconsistencies remain, report them explicitly and propose the smallest concrete fix.
+
+Completion rule:
+- Do not present the task as complete until all checks are done: (1) secret/leak/security scan, (2) critical consistency self-review, (3) consistency check against `/grill-with-docs` artifacts (`CONTEXT.md`, `CONTEXT-MAP.md`, relevant ADRs).
\ No newline at end of file
diff --git a/Readme.md b/Readme.md
index 8d361570..6a4bdd10 100644
--- a/Readme.md
+++ b/Readme.md
@@ -73,37 +73,37 @@ Share ideas 💡 in [issues](https://github.com/vict0rsch/PaperMemory/issues) an
## Supported venues
-- Arxiv
- - PaperMemory will try to find if a pre-print has been published and create a corresponding `note` to the paper (see [preprints](#preprints))
- - Also detects and matches papers from [huggingface.co/papers](https://huggingface.co/papers), [AlphaXiv](https://alphaxiv.org), [ar5iv.org](https://ar5iv.org) and [scirate.com/](https://scirate.com/)
-- BioRxiv
-- NeurIPS
-- Open Review (ICLR etc.)
-- Computer Vision Foundation (I/ECCV, CVPR etc.)
-- Proceedings of Machine Learning Research (PMLR) (AISTATS, ICML, CoRL, CoLT, ALT, UAI etc.)
-- Association for Computational Linguistics (ACL) (EMNLP, ACL, CoNLL, NAACL etc.)
-- Proceedings of the National Academy of Sciences (PNAS)
-- SciRate
-- Nature (Nature, Nature Communications, Nature Machine Intelligence etc.)
-- American Chemical Society (ACS)
-- IOPscience
-- PubMed Central
-- International Joint Conferences on Artificial Intelligence (IJCAI)
-- Association for Computing Machinery (ACM)
-- IEEE
-- Springer (books, chapters and, of course, articles)
-- American Physical Society (APS)
-- Wiley (Advanced Materials, InfoMat etc.)
-- Science Direct
-- Science (Science, Science Immunology, Science Robotics etc.)
-- FrontiersIn (Frontiers in Neuroscience, Frontiers in Neuroscience, Frontiers in Microbiology etc.)
-- PLOS
-- MDPI
-- Oxford University Press
-- HAL Archives ouvertes
-- Royal Society of Chemistry
-- [Sci-Hub](https://papermemory.org/faq/#can-i-reference-my-pdf-in-papermemory)
-- [Add more](https://github.com/vict0rsch/PaperMemory/issues/13)
+- Arxiv
+ - PaperMemory will try to find if a pre-print has been published and create a corresponding `note` to the paper (see [preprints](#preprints))
+ - Also detects and matches papers from [huggingface.co/papers](https://huggingface.co/papers), [AlphaXiv](https://alphaxiv.org), [ar5iv.org](https://ar5iv.org) and [scirate.com/](https://scirate.com/)
+- BioRxiv
+- NeurIPS
+- Open Review (ICLR etc.)
+- Computer Vision Foundation (I/ECCV, CVPR etc.)
+- Proceedings of Machine Learning Research (PMLR) (AISTATS, ICML, CoRL, CoLT, ALT, UAI etc.)
+- Association for Computational Linguistics (ACL) (EMNLP, ACL, CoNLL, NAACL etc.)
+- Proceedings of the National Academy of Sciences (PNAS)
+- SciRate
+- Nature (Nature, Nature Communications, Nature Machine Intelligence etc.)
+- American Chemical Society (ACS)
+- IOPscience
+- PubMed Central
+- International Joint Conferences on Artificial Intelligence (IJCAI)
+- Association for Computing Machinery (ACM)
+- IEEE
+- Springer (books, chapters and, of course, articles)
+- American Physical Society (APS)
+- Wiley (Advanced Materials, InfoMat etc.)
+- Science Direct
+- Science (Science, Science Immunology, Science Robotics etc.)
+- FrontiersIn (Frontiers in Neuroscience, Frontiers in Neuroscience, Frontiers in Microbiology etc.)
+- PLOS
+- MDPI
+- Oxford University Press
+- HAL Archives ouvertes
+- Royal Society of Chemistry
+- [Sci-Hub](https://papermemory.org/faq/#can-i-reference-my-pdf-in-papermemory)
+- [Add more](https://github.com/vict0rsch/PaperMemory/issues/13)
[📑—About finding published papers from preprints](https://papermemory.org/features/#preprint-matching)
@@ -143,11 +143,11 @@ Checkout [📑—All configuration options](https://papermemory.org/configuratio
In the extension's [📑—`options`](https://papermemory.org/configuration/#advanced-options) (right click on the icon or in the popup's menu) you will find advanced customization features:
-- **Auto-tagging**: add tags to papers based on regexs matched on authors and titles
-- **Source filtering**: filter out some paper sources you don't want to record papers from
-- **Custom title function**: provide Javascript code to generate your own web page titles and pdf filenames based on a paper's attributes
-- **Data management**: export/load your memory data and export the bibliography as a `.bib` file
-- **Online Synchronization**: use Github Gists to sync your papers across devices
+- **Auto-tagging**: add tags to papers based on regexs matched on authors and titles
+- **Source filtering**: filter out some paper sources you don't want to record papers from
+- **Custom title function**: provide Javascript code to generate your own web page titles and pdf filenames based on a paper's attributes
+- **Data management**: export/load your memory data and export the bibliography as a `.bib` file
+- **Online Synchronization**: use Github Gists to sync your papers across devices
@@ -195,5 +195,5 @@ See [📑—how it works](https://papermemory.org/features/#code-repositories).
## Todo
-- [ ] Improve `Contributing.md`
-- [ ] Write many more tests! **Help is wanted** (it's not so hard to write unittests 😄) (see `Contributing.md`)
+- [ ] Improve `Contributing.md`
+- [ ] Write many more tests! **Help is wanted** (it's not so hard to write unittests 😄) (see `Contributing.md`)
diff --git a/SOURCE_CODE_REVIEW.md b/SOURCE_CODE_REVIEW.md
new file mode 100644
index 00000000..b82c63af
--- /dev/null
+++ b/SOURCE_CODE_REVIEW.md
@@ -0,0 +1,8 @@
+# Build from source
+
+```bash
+npm install
+npm run zip:firefox
+```
+
+Output: `dist/papermemory--firefox.zip`
diff --git a/contributing.md b/contributing.md
index 9a3601ad..9921818d 100644
--- a/contributing.md
+++ b/contributing.md
@@ -4,187 +4,353 @@
PaperMemory is pure JS+HTML with minimal dependencies: no framework, (almost) no external dependencies so it's easy to help :)
-The only external deps. are [`select2.js`](https://select2.org/) which requires `JQuery` and some of the latter here and there (but I'm working on getting rid of it, replacing it with a simple set of helper functions in `src/shared/utils/miniquery.js`).
+The only external deps are [`tom-select`](https://tom-select.js.org/) (tag inputs) and [`jQuery`](https://jquery.com/) (content-script page UI only) — both installed via npm.
-`npm` and `gulp` are here to make the dev+release lifecycle easier, if you don't want to set it up there still are a lot of things you can help with in the raw source code (just don't bother with the `min` files)
+The project uses modern ES modules and [WXT](https://wxt.dev/) for bundling, dev server, manifest generation, and cross-browser packaging.
+
+### Related documentation
+
+**This guide covers how to set up, build, and contribute** (commands, conventions, step-by-step how-tos, tests, release). For **how the system works** — the mental model, data flow, core abstractions, component responsibilities, and design rationale — see [`system-architecture.md`](system-architecture.md) (e.g. its [Technical Stack](system-architecture.md#technical-stack) for the full dependency and tooling list).
## Set-up
-1. [Install `yarn`](https://classic.yarnpkg.com/lang/en/docs/install): Node's package manager
-2. [Install `gulp`](https://gulpjs.com/): a build tool
-3. Install dependencies: from the root of this repo `$ yarn install`
-4. Watch file changes: `$ gulp watch`
-5. Edit files!
-
-`gulp` mainly runs the concatenation of files into a single one (especially for css and js) and its minification.
-
-In `popup.html` you will notice:
-
-```html
-
-
-
-
-
-
-
-
-
-
-
-
-
+1. [Install `npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) (Node.js 22+ recommended)
+2. Install dependencies: `npm install`
+3. Start the dev server: `npm run dev`
+4. Load the extension in Chrome from `dist/chrome-mv3/` (see [Loading the extension](#loading-the-extension))
+5. Edit files — WXT auto-reloads!
+
+## What is WXT?
+
+[WXT](https://wxt.dev/) is a framework for building browser extensions:
+
+- **Bundling**: Uses Vite under the hood to bundle ES modules
+- **Manifest generation**: Writes `manifest.json` automatically from `wxt.config.js` (Chrome MV3 and Firefox MV2)
+- **Dev server**: Hot module reloading with `npm run dev`
+- **Zip packaging**: `npm run zip` produces ready-to-submit archives for both browsers
+
+You do **not** need to know WXT internals to contribute. The key thing to know is that WXT discovers entry points from `src/entrypoints/` and builds them into `dist/`. For how WXT (and these entry points) fit the overall architecture, see [`system-architecture.md` → System Architecture](system-architecture.md#system-architecture).
+
+## Build commands
+
+```bash
+npm run dev # Dev server for Chrome (HMR, auto-reload)
+npm run dev:firefox # Dev server for Firefox
+
+npm run build # Production build for Chrome + Firefox
+npm run build:chrome # Production build for Chrome only
+npm run build:firefox # Production build for Firefox only
+
+npm run zip # Build + zip for both browsers
+npm run zip:chrome # Build + zip for Chrome only
+npm run zip:firefox # Build + zip for Firefox only
```
-Those `@` commands are meant for `gulp` (using the `preprocess` package) to choose whether to use raw, un-minified files for development (`$ gulp watch`) or concatenated and minified ones for production (`$ gulp build`)
+Build output goes to `dist/chrome-mv3/` and `dist/firefox-mv2/`.
+
+## Loading the extension
+
+### Chrome
+
+1. Run `npm run dev` (or `npm run build:chrome`)
+2. Open `chrome://extensions/`, enable "Developer mode"
+3. Click "Load unpacked" and select the `dist/chrome-mv3/` directory
+4. The extension auto-reloads on file changes when using `npm run dev`
+
+### Firefox
+
+1. Run `npm run dev:firefox`
+2. Open `about:debugging#/runtime/this-firefox`
+3. Click "Load Temporary Add-on" and select any file inside `dist/firefox-mv2/`
+
+More info: https://extensionworkshop.com/documentation/develop/temporary-installation-in-firefox/
+
+## Refreshing the extension
-Note: the file loaded in the popup is `src/popup/min/popup.min.html`, _not_ `src/popup/popup.html` so you _have_ to use `gulp watch` to see changes you make to `popup.html` reflected in the actual popup.
+When using `npm run dev`:
-### Refreshing the extension
+- **Popup / options / fullMemory changes**: Auto-reload via HMR
+- **Background script changes**: WXT reloads the service worker automatically
+- **Content script changes**: Require refreshing the web page where the content script runs
-Once you load the local extension as an unpackaged extension, changes that affect the popup will directly take effect, no need to refresh anything.
+### Debugging utilities (`PMDebug`)
-**Content scripts** however, are loaded and not binded to the source so you _have to_ refresh the extension in the settings (and then any web page you want to see changes on) for those to be taken into account.
+A global `PMDebug` object is available in all HTML pages (popup, options, fullMemory, bibMatcher) for inspecting internal state from the browser console:
-## Loading in Firefox
+```javascript
+PMDebug.config.state; // Global state (papers, prefs, etc.)
+PMDebug.config.state.papers; // All stored papers
+PMDebug.data.getStorage(); // Raw chrome.storage access
+PMDebug.urls.parseIdFromUrl("https://arxiv.org/abs/2301.12345");
+PMDebug.paper.isPaper("https://arxiv.org/abs/2301.12345");
+// Per-source handlers (parse, urlToId, toAbs, toPDF, displayId, …)
+PMDebug.sources.getSource("arxiv").toAbs({
+ pdfLink: "https://arxiv.org/pdf/1234.56789v2.pdf",
+ source: "arxiv",
+});
+PMDebug.listAllFunctions(); // Discover everything available
+```
+
+Available modules: `config`, `functions`, `miniquery`, `data`, `paper`, `bibtexParser`, `sync`, `state`, `urls`, `files`, `parsers`, `sources`, `preprintMatching`, `templates`, `handlers`, `memory`.
-https://extensionworkshop.com/documentation/develop/temporary-installation-in-firefox/
+Paper-specific logic lives under `PMDebug.sources`, not on `PMDebug.parsers` (e.g. there is no `PMDebug.parsers.makeArxivPaper`).
+
+The debug system is implemented in `src/debug/debug.js` and imported by each entry point's `main.js`.
## Conventions
### File structure
-(slightly deprecated since some files have moved but names are unique enough for you to still understand I hope)
-
-```tree
-├── jsconfig.json ➤➤➤ vscode js config
-├── manifest.json ➤➤➤ the extension's configuration file for the browser
-└── src ➤➤➤ actual code
- ├── background ➤➤➤ background code
- │ └── background.js ➤➤➤ background.js can interact with some browser apis content_scripts can't use
- ├── content_scripts
- │ ├── content_script.css ➤➤➤ styling for pages modified by the content_script
- │ ├── content_script.js ➤➤➤ the code run at the opening of a page matched in manifest.json
- ├── popup
- │ ├── css ➤➤➤ The popup's style
- │ │ ├── dark.css ➤➤➤ Dark mode style sheet
- │ │ ├── options.css ➤➤➤ Sliding checkboxes style
- │ │ ├── popup.css ➤➤➤ Main style
- │ │ ├── select2.min.css ➤➤➤ Style for the select2 dropdowns (don't modify)
- │ ├── js ➤➤➤ The javascript files specific to the popup, minified together in this order into js.min.js
- │ │ ├── handlers.js ➤➤➤ Event handlers
- │ │ ├── memory.js ➤➤➤ Memory-specific functions
- │ │ ├── popup.js ➤➤➤ Main execution
- │ │ ├── theme.js ➤➤➤ The first thing that is executed when the popup is opened: selecting dark/light theme based on user preferences
- │ │ ├── select2.min.js ➤➤➤ JQuery-based tagging lib for paper tags
- │ │ └── templates.js ➤➤➤ HTML string templates: memory items and paper popup
- │ ├── min
- │ │ └── minified scripts
- │ ├── popup.html ➤➤➤ Main HTML file
- └── shared
- ├── jquery.min.js ➤➤➤ JQuery lib. Do not modify.
- ├── utils ➤➤➤ Shared utility functions minified together in this order into utils.min.js
- │ ├── bibtexParser.js ➤➤➤ Class to parse bibtex strings into objects
- │ ├── config.js ➤➤➤ Constants / State variables used throughout out the code
- │ ├── data.js ➤➤➤ Data/Memory manipulation (migrations, paper validation, overwrite etc.)
- │ ├── functions.js ➤➤➤ Utility functions, relying on config.js
- │ ├── logTrace.js ➤➤➤ Single var script to include the log stack trace in dev (gulp watch)
- │ ├── miniquery.js ➤➤➤ Custom vanilla js replacement for JQuery (working towards removing that dependency)
- │ ├── parsers.js ➤➤➤ Parsing functions to create papers
- │ ├── paper.js ➤➤➤ Single-paper-related functions (isPaper, paperToAbs, paperToPDF)
- │ └── state.js ➤➤➤ State-related functions (init, custom title function, addOrUpdatePaper etc.)
- ├── utils.min.js ➤➤➤ Concatenation and minification of all files in src/shared/utils/
- └── loader.css ➤➤➤ the style for the loader before the BibTex entry is displayed on arxiv.org/abs/*
```
+├── wxt.config.js ➤ WXT + Vite config (manifest, aliases, HTML include plugin)
+├── jsconfig.json ➤ VS Code config with path aliases (@pm, @pmu)
+├── public/ ➤ Static assets copied verbatim to build output
+│ ├── theme.js ➤ Dark mode detection (runs before modules, loaded via
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Bib Matcher
-
-
-
-
-
-
-
- Paste the content of your .bib file and PaperMemory will automatically match the Arxiv
- entries
- to publications by
- fetching information from DBLP, Semantic Scholar, CrossRef and Google Scholar.
-
-
-
- The matching procedure is identical to the one you can trigger to match your PaperMemory ArXiv
- entries in the
-
- Advanced Options
-
-
-
-
Warning: the matching procedure produces an output
- which
- does
- NOT maintain comments. Select
- matched
- entries one by one if you want to keep your initial comment structure.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
When a pre-print is matched to a publication, the
- latter may have a
- standard citation key. You can either update the entry's citation key or use the new one.
- Note that if you use the new one, your existing citations will be broken.
-
-
-
-
-
-
-
-
Data providers protect their APIs by restricting
- the
- number of queries per seconds you can make. PaperMemory's BibMatcher may iterate too fast
- over your entries, reaching the API rate limits. Adding a timeout between requests slows
- down the matching process but increases your matching rate.