Implemented OpenSpec and created and updated its artifacts
This commit is contained in:
@@ -1,7 +1,14 @@
|
||||
# Architecture and Compatibility Contracts
|
||||
|
||||
This document describes the integrated modular implementation. Verification and
|
||||
preexisting risks are tracked in [refactoring.md](refactoring.md).
|
||||
This document describes the current checkout, including behavior added after the
|
||||
1.1.0 release. [refactoring.md](refactoring.md) records the earlier modularization;
|
||||
historical observations there do not override the current contracts below.
|
||||
|
||||
The [OpenSpec capability baseline](../openspec/specs/) and its archived
|
||||
[source/test evidence map](../openspec/changes/archive/2026-10-31-document-project-baseline/design.md)
|
||||
provide behavioral context. The initial change was completed and archived on
|
||||
2026-10-31, promoting all twelve capabilities to durable specifications. Use those
|
||||
capabilities for subsequent delta changes.
|
||||
|
||||
## Sources, Outputs, and Runtime Boundaries
|
||||
|
||||
@@ -12,17 +19,24 @@ preexisting risks are tracked in [refactoring.md](refactoring.md).
|
||||
- `dist/chromium` serves Chrome and Edge; `dist/firefox` serves Firefox. Do not edit outputs.
|
||||
- Both manifests use V3. Chromium has a classic background service worker; Firefox
|
||||
uses `background.scripts`. Neither entry point is declared as a module.
|
||||
- Content scripts run at `document_idle`, in all matching frames on the two
|
||||
- `src/content/shortcut-intent.js` runs at `document_start` in the top frame,
|
||||
preserving bookmark intent before Fusion rewrites the URL. The main content
|
||||
script runs at `document_idle`, in all matching frames on the two
|
||||
configured Fusion welcome pages. Their code also checks the hostname/path.
|
||||
- `src/auth/spa-page.js` is a separate top-frame-only SPA content script, copied
|
||||
- `src/auth/spa-page.js` runs at `document_end` as a separate top-frame-only SPA content script, copied
|
||||
into both distributions. It handles only extension messages for DOM readiness
|
||||
inspection and a fixed same-origin identity GET; it sends no requests on its own.
|
||||
`services/spa/page-client.js` connects this receiver to the background recovery
|
||||
flow and the existing debug envelope. No page-world script is injected.
|
||||
- There are no extension UI pages, injected page-context scripts, custom CSP,
|
||||
declared execution world, or web-accessible resources.
|
||||
- Permissions remain `cookies` and `storage`, with host access limited to the
|
||||
configured Fusion host and `spa.oracle.com`.
|
||||
- `scripts/build.mjs` defines permissions: `bookmarks`, `cookies`, `storage`,
|
||||
`webRequest` and `declarativeNetRequestWithHostAccess`. Host permissions cover
|
||||
`eeho.fa.us2.oraclecloud.com`, `spa.oracle.com`, and
|
||||
`comcipapic-oalprod.integration.ocp.oraclecloud.com` over HTTPS.
|
||||
- Bookmark access supports folder selection and shortcut detection. SR integration
|
||||
observes App Builder client IDs and installs narrow session rules for required
|
||||
Origin headers; it is not a general request proxy.
|
||||
|
||||
Content owns the DOM and per-frame state. Background owns privileged cookies,
|
||||
temporary tabs, extension storage and SPA requests. These contexts communicate
|
||||
@@ -40,41 +54,67 @@ different from extension storage and is not a safe place for credentials.
|
||||
| Content | Plan projection details | Per-panel WeakMap, shared in-flight requests, nested tooltip DOM, temporary pointer/resize listeners cleaned up on dismiss/tab change/modal close |
|
||||
| Content | Rate card | Pure column/filter/sort model, controller with per-plan in-flight sharing, animated drawer with skeleton/search/table; response guards and cleanup on close or plan switch |
|
||||
| Content | Service Requests UI | Customer-specific in-flight queries, skeleton/table modal, stale-response guards; no persistent cache |
|
||||
| Content | AI summary | Per-opportunity in-flight state and summary; fresh generation on expansion |
|
||||
| Content | Authentication/debug | General and scoped token promises, SPA authorizations, request logs, visibility flag |
|
||||
| Content | Organization Chart | Working hierarchy, contact autocomplete/creation, transient email and contact-opportunity views |
|
||||
| Content | Manage Time | Week drafts, dependent selections, validated hours and deferred writes/deletions |
|
||||
| Content | Authentication/debug | General token promise, SPA authorizations, request records and visibility state |
|
||||
| Background | Message dispatcher | Synchronously registered listener, asynchronous replies using `return true` |
|
||||
| Background | Products | Four workers, per-opportunity in-flight map, lazy cache read and serialized writes |
|
||||
| Background | Cookie/session recovery | Inactive tabs, polling/retry delays, cleanup in `finally` |
|
||||
| Background | SPA operations | HTTP/JSON parsing, account/plan extraction and debug envelopes |
|
||||
| Background | Organization Chart storage | User/customer-scoped IndexedDB records and revision checks |
|
||||
| Background | SR and time integration | Session preparation, resource lookup, service operations and header adaptation |
|
||||
|
||||
Closing the main modal hides it rather than destroying it. Many requests can
|
||||
continue while it is hidden. Version and request-ID checks suppress stale UI
|
||||
updates; they do not abort network traffic. No AbortController is currently used.
|
||||
updates; they do not generally abort network traffic. Token Relay specifically
|
||||
uses a 30-second timeout with AbortController when available. Its timeout also
|
||||
covers response reading; debug persistence failures cannot leave it pending.
|
||||
Some listeners have window guards; there is no general disposal lifecycle.
|
||||
|
||||
## Authentication and API Routing
|
||||
|
||||
Never substitute one token family for another or assume an opaque token is a JWT.
|
||||
Production tokens are held in memory, not deliberately persisted by the extension.
|
||||
Normal authorization reuse and debug capture have different storage lifetimes;
|
||||
see Storage Contracts before making claims about retained sensitive values.
|
||||
|
||||
| Credential | Acquisition | Consumers | Lifetime/recovery |
|
||||
| --- | --- | --- | --- |
|
||||
| XSRF | Background cookie lookup, then content `document.cookie` fallback | `x-xsrf-token` for both tokenrelays | Cookie recovery tab on missing/invalid response; existing retry rules |
|
||||
| Fusion general | GET `/fscmRestApi/tokenrelay` | Opportunity POST and product GET Bearer | Content promise/cache; no expiry-based refresh equivalent to Sales Common |
|
||||
| Sales Common | GET `/fscmRestApi/tokenrelay?scopesfq=urn%3Aopc%3Aresource%3Afusion%3Aeeho%3Asales-common%2F` | AI summary Bearer | In-memory `expires_in`, shared in-flight acquisition; at most one summary retry after 401/403 |
|
||||
| XSRF | Background cookie lookup, then content `document.cookie` fallback | `x-xsrf-token` for Token Relay | Cookie recovery on missing/invalid response with a bounded refresh attempt |
|
||||
| Fusion general | GET `/fscmRestApi/tokenrelay` | Opportunity/saved-search, product, CRM SR and contact queries/writes | Shared content acquisition; 30-second request timeout |
|
||||
| ACCP-TS | SPA consumer service `uiaasHeader.value` | Account summary authorization | Retrieved with the consumer service response; expiry recorded, not automatically acted on |
|
||||
| OCICONS-TS | SPA consumer service `uiaasHeader.value` | Account plans, consumption trend, plan detail and rate card authorization | Same service response, distinct credential from ACCP-TS |
|
||||
| SPA-ORANGE | SPA consumer service `uiaasHeader.value` | Customer service requests, fixed `custsr-auth` header with raw value, no Bearer prefix; ignores `uiaasHeader.label` | Optional at consumer acquisition; required only when opening Service Requests; existing in-memory session rules |
|
||||
| SAIB-TS | SPA consumer service label/value | Installed Base after account registry resolution | Required when opening Installed Base |
|
||||
| WWB-TS | SPA consumer service value | Workloads and Actions in `Wwb-Provider-Authorization` | Shared consumer acquisition; data caching is separately scoped |
|
||||
| SR integration session | Integration cookies and `Authorization: Session` | Internal SR and Manage Time operations; catalog preparation | Required Origin and observed `x-appbuilder-client-id` where applicable; session preparation is separate from identity |
|
||||
| Catalog authorization | SR catalog/token preparation | SR creation | Operation-specific headers; do not replace with the Fusion general token |
|
||||
|
||||
Fusion calls use `https://eeho.fa.us2.oraclecloud.com`; SPA calls use
|
||||
`https://spa.oracle.com/oalcrm/web/api/`. Preserve `credentials: include` and
|
||||
operation-specific headers rather than unifying all calls under a default token.
|
||||
|
||||
Identity is validated before dependent data requests and then reused. Content
|
||||
shares its pending identity promise; background shares by cookie store/private
|
||||
context and can restore user id/name/roles from extension session storage. A page
|
||||
reload alone therefore does not force identity revalidation. SALES_REP blocks
|
||||
CRM activity data, badges and Manage Time entry points. Independent SR session
|
||||
navigation can start concurrently before the role result is consumed.
|
||||
|
||||
SPA bootstrap uses
|
||||
`https://spa.oracle.com/oalcrm/web/api/g2m-consumer-application/ui/index.html?ojr=whitespace`.
|
||||
Recovery rejects login/error/callback documents before a same-origin identity
|
||||
probe. It closes owned tabs and preserves the originating window/container.
|
||||
SR preparation uses the SeaaS_initiation external application, with a 70-second
|
||||
timeout and manual retry. Internal SR detail recovery instead uses a hidden iframe
|
||||
and bounded, shared 401 checks, not a visible tab or an unlimited request loop.
|
||||
|
||||
| Request | Method/path | Important contract |
|
||||
| --- | --- | --- |
|
||||
| requestOpportunities | POST `/crmRestApi/searchResources/11.13.18.05/custom-actions/queries` | RecordSet OR plus EffectiveDate AND, original fields, Preference transient, pages of 15 |
|
||||
| requestOpportunitiesSavedSearch | GET `/crmRestApi/searchResources/11.13.18.05/custom-actions/queries` | Opportunity entity; require one `isDefault=true` result with a valid savedSearchNumber |
|
||||
| requestOpportunities | POST `/crmRestApi/searchResources/11.13.18.05/custom-actions/queries` | `copiedFrom=queries/<default savedSearchNumber>` across pages; RecordSet OR plus EffectiveDate AND; Preference transient; pages of 15 |
|
||||
| requestSRSearchResources | GET `/crmRestApi/searchResources/latest/custom-actions/queries` | CRMServiceRequest entity; use the first item's savedSearchNumber |
|
||||
| resquestCrmServiceRequest | POST `/crmRestApi/searchResources/latest/custom-actions/queries` | Dynamic copiedFrom; pending-delivery team SRs; all pages; incremental badge updates |
|
||||
| requestOpptyProducts | GET `/crmRestApi/rest/rv:be91c002-2e5d-4ed2-a37e-e0c837bf141f/en/11.13.18.05:9/opportunities/{number}/child/ChildRevenue` | Encoded identifier, original query/fields/order, count/offset pagination, maximum 250 pages |
|
||||
| requestGenerateAiSummary | POST `/api/sales-common/gen-ai/v1/summarizations/generate` | `{path,promptCode,object,puid}`; prompt `cx.sales.generate_opportunity_summary`; only summaryText displayed |
|
||||
| requestConsumerTokenService | GET `g2m-consumer-application/consumerTokenService` | No-store; parse JSON; HTML 200 recovery via inactive SPA UI tab; maximum five HTTP attempts |
|
||||
| requestAccountSummary | POST `provider-proxy/g2m-account/service/accountsummary` | ACCP-TS; `[{key:"regId",value:partyNumber}]` |
|
||||
| requestCustomerServiceRequest | GET `provider-proxy/g2m-custsr-provider/service/oscSr/{accountSummaryId}` | SPA-ORANGE; summary `id`, not `registryId`; encoded filter `{hierarchy:"single",status:"open"}`, creationDate descending, pages of 40 using totalResults/count/offset |
|
||||
@@ -87,9 +127,23 @@ SPA parsing removes BOM and the existing anti-JSON-hijacking prefixes before
|
||||
JSON.parse. Content uses response.json directly. Do not silently unify these behaviors.
|
||||
POST retry policies require operation-specific review; do not add generic retries.
|
||||
|
||||
After all opportunity pages arrive, the ready list is displayed before background
|
||||
SR and product loading start. Completed product cycles use successful/reused SR
|
||||
results before Workload Actions. Cached products can still require activity
|
||||
network reads on cache miss. Failed SR/Action lookups remain unknown rather than
|
||||
confirmed absent. Actions currently fetch only their first page and disclose
|
||||
`hasNext`; contact opportunities and account workloads consolidate all pages.
|
||||
|
||||
Contact search uses Fusion `searchResources/latest` POST with a customer PartyId
|
||||
criterion, keywords, Preference transient and the general Bearer. Contact
|
||||
opportunities use the person's opaque PartyNumber in the GET filter, including
|
||||
alphanumeric identifiers. Contact creation uses the selected customer's account
|
||||
identifiers and an explicit Save; local hierarchy persistence is independent.
|
||||
|
||||
## Message Contracts
|
||||
|
||||
All names below have the `opportunitiesExtension.` prefix. Unknown messages return
|
||||
The examples below have the `opportunitiesExtension.` prefix; the complete name
|
||||
inventory is in `src/shared/messages.js`. Unknown messages return
|
||||
false without a response. Supported background messages return true immediately
|
||||
and later call sendResponse. `platform/background-api.js` centralizes the existing
|
||||
callback/promise differences. `platform/content-messages.js` retains the original
|
||||
@@ -121,25 +175,94 @@ Do not expose a caller-provided arbitrary request URL through the background.
|
||||
| Page localStorage: `opportunities-extension-filter-preferences` | JSON object: period, stages, statuses, customerKeys, ownerKeys, customerSearch, ownerSearch, tableSearch; unset customer/owner selections may be null |
|
||||
| Page localStorage: `opportunities-extension-theme` | `light` or `dark` |
|
||||
| Page localStorage: `opportunities-extension-opportunity-type-preferences` | JSON object: opportunityTypeValues, opptyProductsCacheHours |
|
||||
| Page localStorage: `opportunities-extension-table-sort-v1-<table>` | Sort key and direction, independently per supported table; memory fallback if storage is unavailable |
|
||||
| Page localStorage: `opportunities-extension-manage-time-reference-date` | Valid selected date; falls back to today |
|
||||
| Page localStorage: `opportunities-extension-debug:<tabId>:<id>` and `:index` | Full captured request records and index; may retain sensitive originals; quota failures fall back to memory |
|
||||
| Page sessionStorage: `opportunities-extension-debug-tab` | Namespace identifier for debug records |
|
||||
| Page sessionStorage: `opportunitiesExtension.shortcutIntent` | Early bookmark timestamp; consumed once and expires after ten minutes |
|
||||
| Extension storage.local: `opportunitiesExtension.opptyProductsCache.v1` | Object indexed by OptyNumber, each entry `{fetchedAt,items}` |
|
||||
| Extension storage.local: `opportunitiesExtension.workloadsCache.v1` | Workload data by normalized principal/customer; `{fetchedAt,items}`; no persistent entry without a principal |
|
||||
| Extension storage.local: `opportunitiesExtension.activities.v1:<scope>` | SR/Action records scoped by principal, kind and identifier; SR keys also include product-update timestamp |
|
||||
| Extension storage.local: `opportunitiesExtension.activities.invalidatedAt.v1` | Invalidation timestamp preventing obsolete activity snapshots from being reused or rewritten |
|
||||
| Extension storage.session: `opportunities-extension-current-user:<context>` | Validated `{id,name,roles}`, partitioned by cookie store/private context; no identity token in this cache |
|
||||
| Extension storage.session: `opportunitiesExtension.resourceUsers.v1` | Resource user, email and fetchedAt; shared concurrent reads and TTL validation |
|
||||
| Extension storage.session: `opportunitiesExtension.srAppBuilderClientId.v1` | Observed integration origin/client ID/timestamp; one-hour validity |
|
||||
| Extension-owned IndexedDB: `opportunities-extension-organizations`, store `charts` | Database version 3; user/customer key, record schemaVersion 1, revision, updatedAt and normalized nodes |
|
||||
| Page memory: form request cache | Allowlisted read results, shared in-flight promises, maximum 500 entries, scoped to current user |
|
||||
| Page memory: active views and authentication | Working charts, transient contact email, week drafts, opportunity results, token promises and service authorization values |
|
||||
|
||||
Product TTL defaults to 24 hours and is capped at 30 days in background. Storage
|
||||
failures are tolerated for the active view. Recreating background reloads stored
|
||||
products but does not restore in-flight jobs. Cache TTL does not physically delete
|
||||
expired entries. Account/container isolation limitations are tracked separately.
|
||||
Product TTL defaults to 24 hours and is capped at 30 days by the background cache.
|
||||
Activity TTL uses the Preferences duration, also capped at 30 days, anchored to
|
||||
the product-update timestamp rather than response time. SR snapshots preserve
|
||||
different update timestamps; expired SR snapshots are pruned on subsequent writes.
|
||||
The form request cache defaults to 24 hours and caps its duration at seven days;
|
||||
it passes that duration to supported operations, including workload/resource reads.
|
||||
Workload reuse preserves the original timestamp instead of restarting the TTL.
|
||||
|
||||
The product cache is keyed by opportunity number, not user or container. Do not
|
||||
extend the stronger principal/customer scoping of workload and activity caches to
|
||||
that cache by assumption. Identity caching has browser-session lifetime and its
|
||||
own cookie-store/private-context partition, with memory fallback where extension
|
||||
session storage is unavailable. Browser storage APIs are distinct from page storage.
|
||||
|
||||
Form cache reuse excludes writes and token generation. Successful supported writes
|
||||
invalidate reusable form reads; concurrent reads share promises and suppress
|
||||
duplicate debug envelopes. Refresh all invalidates related product, workload,
|
||||
activity and form data before reloading Reference, while preserving preferences
|
||||
and authentication. Generation checks prevent old reads from restoring invalidated
|
||||
snapshots. Invalidation failure is surfaced rather than silently accepted.
|
||||
|
||||
Organization-chart nodes retain id, parentId, name, jobTitle, notes, collapsed,
|
||||
optional contactPartyId and alphanumeric PartyNumber. Email is excluded; older
|
||||
stored email fields are removed during migration. Chart storage is isolated by
|
||||
normalized user/customer and browser profile, enforces revision checks, and has no
|
||||
cross-device sync. Clearing a Fusion page's IndexedDB does not clear the extension's
|
||||
IndexedDB, and an already-open working chart can still exist in page memory.
|
||||
|
||||
Storage failures have feature-specific behavior: preferences and data caches can
|
||||
fall back to current memory/results, debug keeps records in memory with feedback,
|
||||
and chart persistence reports failure rather than claiming a durable save.
|
||||
Recreating the background loses in-flight jobs while restoring supported stored
|
||||
data on demand. Expiration does not imply that every cache physically deletes its
|
||||
old records immediately.
|
||||
|
||||
## UI Contracts
|
||||
|
||||
### Debug lifecycle
|
||||
|
||||
`features/debug/state.js` stores each changed request under the current tab's
|
||||
localStorage prefix and maintains an index, batching writes with a short timer.
|
||||
Opening Debug Requests reloads saved records while retaining handles used by
|
||||
in-flight requests. Hidden debug does not construct request-detail DOM; opening
|
||||
renders summaries and expanding a record mounts details. Storage exceptions keep
|
||||
the memory records usable and do not block the recorded network operation.
|
||||
|
||||
`services/fusion/opportunities.js` resets debug immediately before sending page
|
||||
one, after resolving the saved search. Thus earlier authentication and saved-search
|
||||
logs can disappear at that boundary. Pagination does not reset history, and closing
|
||||
the main modal does not delete it. Reset clears only the current tab prefix and
|
||||
invalidates previous-generation completions, preserving preferences and other tabs.
|
||||
|
||||
The default UI masks sensitive values and the explicit visibility toggle can show
|
||||
retained originals, including tokens in captured responses. Individual transports
|
||||
also mask selected headers, but that is not a universal redaction guarantee.
|
||||
localStorage records can contain sensitive values; display masking is neither
|
||||
encryption nor protection from other code with access to that page's storage.
|
||||
The normal identity cache excludes its token, which does not imply the debug
|
||||
response excludes it. This documentation change does not alter retention policy.
|
||||
|
||||
### Shared interactions
|
||||
|
||||
Preserve labels/IDs/classes, column order, URLs, clipboard behavior, sort/filter
|
||||
semantics, formatting, theme colors, keyboard shortcuts and row expansion.
|
||||
WON includes WON_PENDING without rewriting StatusCode. Owner uses first/last name
|
||||
with the existing fallbacks. Changing reference resets customers/owners to all;
|
||||
closing/reopening preserves search. Fiscal years begin in June; RENEWALS currently
|
||||
has no date range and therefore does not load opportunities.
|
||||
closing/reopening preserves search. Fiscal years begin in June; the active
|
||||
Reference choices use supported fiscal/date ranges rather than a RENEWALS view.
|
||||
|
||||
Preserve SVG chart data normalization, animation, tooltips and series selection.
|
||||
AI text is inserted with textContent. Debug masking and DOM isolation have known
|
||||
limitations; see the separate security backlog rather than assuming full redaction.
|
||||
Treat displayed API text as data. Debug masking and DOM isolation have limitations;
|
||||
do not equate display masking with full redaction or encryption at rest.
|
||||
|
||||
## Module Ownership and Dependencies
|
||||
|
||||
@@ -151,8 +274,9 @@ to isolated tests, not published on the page/global object in production.
|
||||
Factories do not import or instantiate peers. Features receive service/auth ports;
|
||||
services/auth receive transport/platform ports; pure models receive data. The
|
||||
composition roots supply callback notifications for UI updates and debug events,
|
||||
preserving existing interactions without module-import cycles. Shared messages
|
||||
are the only source included in both contexts, with distinct instances/state.
|
||||
preserving existing interactions without module-import cycles. Shared messages,
|
||||
date/debug utilities and the organization-chart model can be included in both
|
||||
contexts, with distinct instances/state.
|
||||
Some stateful UI collaborators intentionally share a feature-owned state object;
|
||||
these objects are created per application, never stored in a global registry.
|
||||
|
||||
@@ -163,11 +287,12 @@ these objects are created per application, never stored in a global registry.
|
||||
| fiscal dates/query payload | `features/opportunities/fiscal-periods.js`, `services/fusion/opportunities.js` | Pure date rules vs HTTP operation |
|
||||
| table/search/filters/formatting/dashboard | `features/opportunities/` | Related presentation responsibilities, explicit state |
|
||||
| saved settings | `features/preferences/storage.js`, `view.js` | Serialization/fallbacks vs dialog |
|
||||
| token families | `auth/content-session.js`, `sales-common.js`, `token-relay.js`, `spa-credentials.js` | Different credentials and lifetime policies |
|
||||
| token families | `auth/content-session.js`, `current-user.js`, `token-relay.js`, `spa-credentials.js`, `sr-request-headers.js` | Different credentials and lifetime policies |
|
||||
| cookie lookup/recovery, SPA session | `auth/xsrf-*.js`, `consumer-session.js` | Privileged recovery separate from UI |
|
||||
| product queue/cache | `features/products/background-jobs.js`, `cache.js`, `services/fusion/products.js` | Scheduling, persistence and pagination |
|
||||
| product progress/details | `features/products/content-jobs.js`, `details.js` | Message consumer vs row rendering |
|
||||
| AI summary | `features/ai-summary/content.js`, `state.js` | Per-opportunity lifecycle, safe text and auth port |
|
||||
| org charts and contacts | `features/org-chart/`, `services/fusion/account-contact.js` | Local hierarchy/storage vs Oracle contact operations |
|
||||
| Manage Time | `features/manage-time/`, `services/fusion/time-entries.js` | Weekly UI drafts vs session-authenticated reads/writes |
|
||||
| consumption | `features/consumption/`, `services/spa/` | Plans, chart model/view/controller vs HTTP operations |
|
||||
| debug | `features/debug/content-log.js`, `content-view.js` | Retention/formatting vs DOM presentation |
|
||||
| background dispatcher | `background/messages.js`, `shared/messages.js` | Stable serializable message contracts |
|
||||
@@ -175,7 +300,7 @@ these objects are created per application, never stored in a global registry.
|
||||
|
||||
The background composition deliberately has no DOM dependency. Listener registration
|
||||
is synchronous, before any storage read or request. Worker restart reconstructs
|
||||
volatile state and lazily reads the existing product cache; tokens are not persisted.
|
||||
volatile state and lazily restores supported product, identity and session metadata.
|
||||
Content effects (DOM, fetch, storage, animation) enter through the environment at
|
||||
the composition root. localStorage access stays lazy to retain blocked-storage
|
||||
fallbacks. Tests may inject effects or individual callback ports without changing
|
||||
@@ -183,7 +308,8 @@ source strings. Time-sensitive modules are tested with isolated clocks.
|
||||
|
||||
## Adding a Feature
|
||||
|
||||
1. Define its message/API/storage contract and synthetic expected behavior first.
|
||||
1. Identify the affected OpenSpec capabilities and propose the behavior delta,
|
||||
message/API/storage contract and synthetic expected behavior first.
|
||||
2. Add a factory under the owning feature, or a service under the correct host.
|
||||
Accept narrow function dependencies; do not call another factory inside it.
|
||||
3. Add its source path to the appropriate array in `scripts/sources.cjs`. Source
|
||||
@@ -206,8 +332,6 @@ belong in the consumption feature and SPA service, not the bootstrap.
|
||||
## Source vs Generated Code
|
||||
|
||||
Distribution `content.js` and `background.js` are generated bundles and may remain
|
||||
large. No copy/export/execute JavaScript generator feature was found in the source.
|
||||
AI summaries are text, not executable JavaScript. No empty generators subsystem
|
||||
was added. Build serialization uses JSON.stringify for CSS, retains quotes,
|
||||
large. Build serialization uses JSON.stringify for CSS, retains quotes,
|
||||
backslashes, Unicode and line breaks, and is syntax-checked by vm.Script. Visual
|
||||
fixtures now load composed scripts normally; they no longer eval rewritten sources.
|
||||
|
||||
Reference in New Issue
Block a user