Implemented OpenSpec and created and updated its artifacts

This commit is contained in:
2026-10-31 04:33:34 -03:00
parent 0d0a7cfe14
commit 0d9e654b11
40 changed files with 3303 additions and 127 deletions

341
README.md
View File

@@ -2,30 +2,94 @@
Browser extension that adds the "Opportunities Extension" shortcut to the permitted Oracle Fusion pages, retrieves opportunities, and displays them in a filterable table or calendar.
See [Release Notes](RELEASE_NOTES.md) for version history and
[Architecture](docs/architecture.md) for execution, authentication and storage contracts.
## OpenSpec Workflow
The [twelve durable capability specifications](openspec/specs/) define the reviewed
behavioral baseline for this checkout. The initial
[`document-project-baseline` change](openspec/changes/archive/2026-10-31-document-project-baseline/proposal.md)
was completed and archived on 2026-10-31. Its
[design and evidence map](openspec/changes/archive/2026-10-31-document-project-baseline/design.md)
connect the baseline to source and tests, and its
[tracked tasks](openspec/changes/archive/2026-10-31-document-project-baseline/tasks.md)
record the completed application work. Use the durable specifications as the
starting point for subsequent capability changes.
For subsequent work, explore the problem, propose a change with the affected
capabilities, apply its tasks, verify the outcome, then archive. In Codex these
steps use `$openspec-explore`, `$openspec-propose`, `$openspec-apply-change` and
`$openspec-archive-change`. Application and archival are separate actions.
```bash
openspec list --json
openspec list --specs --json
openspec validate --specs --strict
```
Keep setup/usage guidance here, behavioral requirements in OpenSpec, execution
details in the architecture document and historical releases in RELEASE_NOTES.md.
## Opportunity Views
**Overview** is the default table view. **Calendar** displays opportunities
by Close Date (`EffectiveDate`) with month and week views only. Both views share
Reference, search and all existing filters; switching views does not send requests.
Events use date-only values without time-zone shifts, stage colors, and a read-only
detail with an opportunity link and copy button. Invalid or missing dates appear
in a separate list. Calendar navigation does not change the selected Reference.
**Overview** is the default table view. It gives Name and Customer greater width
priority, uses customer `PartyName`, and labels win probability **Prob.**. Monetary
values and headers align right; other headers align left. Counts and stage amounts
follow the filtered opportunities.
Overview and **Calendar** share Reference, search, Customer, Owner, Status, Stage,
Booking/Workload and eligible SR/Action filters. Selecting Customers narrows the
available Owners, and selecting Owners narrows Customers, using loaded data.
Clicking one choice while all choices are selected isolates that choice; subsequent
clicks can add others. This also applies to Booking/Workload badges. The SR below
4h option has its own exclusive behavior, described under Workload Actions.
Calendar displays Close Date (`EffectiveDate`) in vertically scrolling months
restricted to Reference. There are no previous/next buttons or month/week switch.
Every matching opportunity has a card; crowded days scroll internally instead of
using a hidden-event overflow link. Switching views and filtering loaded records
do not send new opportunity requests. When today is inside Reference, the view
positions around its week with one preceding and one following week where the
Reference allows; otherwise it starts at the beginning of the range.
Cards preserve the API calendar day without timezone shifts and use stage colors.
Clicking opens read-only details with an opportunity link and copy action. Invalid
or missing close dates remain accessible in a separate collapsible list. Fiscal
labels use `FY27-Q2-W19`: June-May fiscal years, Sunday-Saturday weeks, and W1 for
the week containing June 1. The week's Sunday determines its quarter, except W1
belongs to Q1.
Table sorting remembers the last column and direction independently for Overview,
Installed Base, Service Requests, Rate Card, Consumption projection and Manage Time.
Preferences are restored when reopening each view and survive page reloads in the
same browser profile. Opportunity product tables retain their original order.
Each day shows a filtered opportunity count, at most two events and an overflow
link to the remaining events. The grid fits the available height and reduces
visible events to one or zero when needed to keep every week visible. Missing
close dates are available in a collapsible list below the calendar.
Fiscal labels use `FY27-Q2-W19`: June-May fiscal
years, Sunday-Saturday weeks, and W1 for the week containing June 1. A quarter is
assigned by the week's Sunday, except the first fiscal week which belongs to Q1.
Installed Base, Service Requests, Rate Card, Consumption projection, contact
opportunities and Manage Time. Preferences survive reopening and page reloads in
the same browser profile. Opportunity product tables retain their original order.
FullCalendar Standard 6.1.19 is bundled locally under `src/vendor/fullcalendar/`
with its MIT license. It is initialized only on first use, without a runtime CDN.
## Products, Workloads and Projections
Product counts and details load in the background after opportunities are shown.
Workloads are retrieved per distinct customer account, paginated and matched to
products by `RevnId`. Valid workload cache entries are reused across modal openings
and token renewals according to Cache duration.
When a nonempty product list contains only blank or `-` Workload names, the ramp
badge becomes **Booking**, with its own color. If any product has a Workload name,
the ramp behavior remains. Booking and Workload badges beside the view tabs filter
these categories.
Clicking a ramp badge opens **Consumption projection** even when there is no
forecast discrepancy. Difference alerts sit between the ramp and SR badges;
products without a workload record show an unavailable alert in a disabled color.
Projection calculates monthly amounts from adjusted ACR and ramp months, including
first-month proration, and compares `forecast - calculated` at cent precision.
**Update estimates** asks for confirmation before sending the Calculated values
to SPA. It prevents duplicate submission, reports success or failure, refreshes
the comparison after success and invalidates the affected workload cache.
## Customer Organization Charts
The organization-chart icon beside **View customer service requests** opens a
@@ -53,9 +117,29 @@ changes require confirmation before closing. The extension background stores
JSON-compatible records in IndexedDB, scoped by current user and customer ID in
the current browser profile. Records include schema version, revision, update
timestamp and a flat node array (`id`, `parentId`, `name`, `jobTitle`, `notes`,
`collapsed`, and optional `contactPartyId`). Concurrent stale saves are rejected. Charts are local and do not
sync between devices or write to Oracle services. They support up to 500 people
and 60 reporting levels, with required name and optional notes.
`collapsed`, and optional `contactPartyId` and `PartyNumber`). PartyNumber is
preserved as an identifier, including alphanumeric values such as `A4KCS2P`.
Person details persists a returned PartyNumber so it can be reused by subsequent
opportunity lookups. Concurrent stale saves are rejected. Saving the hierarchy
does not write it to Oracle or synchronize it between devices. Charts support up
to 500 people and 60 reporting levels, with required name and optional notes.
The opportunities icon beside the person's delete action opens
**Opportunities | <name>**. It uses that person's stored PartyNumber in
`OpportunityContact.ContactPartyNumber`, resolving it through contact lookup when
missing. It loads all pages while `hasMore=true`, shows a loading skeleton and
provides sortable results. Clicking an opportunity Name opens its corresponding
Fusion opportunity in a new tab. Unresolved identifiers produce an explicit error
instead of an unfiltered opportunity query.
The Create Contact button at the right of **Name** opens an Oracle contact form.
First Name, Last Name and Email Address are required. Job Title and Work/Mobile
Phone are optional; supplied country codes and numbers are combined as
`55-99999999999`. Email and phone consent selections map to the corresponding
Do/DoNot flags. Account identifiers come from the current customer, and absent
optional values are omitted. Save submits directly to the contact API, without a
payload preview. Contact creation is a separate Oracle write from local chart
persistence.
The interaction is inspired by [OrgChart](https://github.com/dabeng/OrgChart).
Layout uses bundled [D3 Hierarchy 3.1.2](https://d3js.org/d3-hierarchy/tree), with
@@ -74,7 +158,7 @@ check below.
- `src/content/`: page integration, modal shell, theme and content composition.
- `src/background/`: synchronous message registration and background composition.
- `src/features/`: opportunities, products, consumption, AI summaries, preferences and debug.
- `src/features/`: opportunities, products, consumption, customer tables, organization charts, Manage Time, preferences and debug.
- `src/auth/`: credential-specific selection, reuse and session recovery.
- `src/services/`: Fusion/SPA operations and background HTTP/JSON transport.
- `src/platform/`: narrow browser messaging, cookies, tabs and storage adapters.
@@ -85,52 +169,66 @@ check below.
- `dist/chromium/`: output for Google Chrome and Microsoft Edge.
- `dist/firefox/`: output for Firefox.
## Authentication and Access
Before dependent data requests, the extension establishes a valid SPA identity.
Concurrent consumers share acquisition; Reference changes and modal reopenings
reuse success. Identity also has a background browser-session cache, partitioned
by Firefox cookie store and private-browsing context, so a page reload does not
necessarily fetch identity again. The validated API response must contain user
id, name, roles and a token; the reusable identity stores only id, name and roles.
Failures keep dependent requests waiting and retry with backoff.
Users with **SALES_REP** do not load CRM SR or Workload Actions data or see their
badges. **Manage Time** is also hidden and its data request entry points are
blocked. Independent SR session bootstrap may still run; readiness does not grant
access to restricted activity data.
When identity requires session recovery, the extension opens
`https://spa.oracle.com/oalcrm/web/api/g2m-consumer-application/ui/index.html?ojr=whitespace`
in an inactive auxiliary tab. SPA inspection rejects error documents, SAML/login
forms and OAuth callbacks before an identity probe. HTTP 200, cookies or elapsed
time alone do not prove readiness. Rejected callbacks restart at this bootstrap
URL. Recovery preserves the originating browser context and cleans up its owned
tab; its bounded cycle can be retried by the waiting identity gate.
Consumer-token recovery uses the same SPA bootstrap URL for invalid JSON and
HTTP 401/403, with up to five HTTP attempts. Token Relay uses the Fusion XSRF
cookie, has a 30-second request timeout and bounded cookie recovery; it cannot
remain indefinitely pending when the timeout fires.
SR session preparation can run concurrently with SPA preparation through
`https://comcipapic-oalprod.integration.ocp.oraclecloud.com/ic/builder/rt/SeaaS_initiation/live/webApps/external/`.
It reports **SR session ready** on success, waits up to 70 seconds, and offers
Retry after failure. Eligible users' resource data is consumed after both identity
and SR preparation finish. See the architecture document for service-specific
credentials and the distinction between session navigation and API requests.
## CRM Service Request Badges
The header's **Refresh all** button (between theme and Preferences) invalidates
persisted Products and activity caches and clears in-memory data views. It reloads
the selected Reference, fetching fresh products and then the permitted SR/Actions
queries. Customer details, rate cards, consumption charts and AI summaries are
The header's **Refresh all** button invalidates persisted Products and activity
caches and clears related in-memory data views. It reloads the selected Reference,
displays opportunities first, then starts permitted SR and product enrichment.
Actions follow their SR/product prerequisites. Customer details, rate cards and consumption charts are
requested again when opened. Preferences, cookies and authentication are preserved.
The button is disabled during invalidation and list reload; product/activity badges
continue to show subsequent background progress. Cache-clear failures are surfaced
instead of silently reusing cached data, and stale product responses cannot restore
an invalidated cache.
On the first Opportunities modal opening in a page session, `requestCurrentUser`
fetches SPA identity before tokenrelay or opportunity requests. Concurrent calls,
Reference changes and modal reopenings reuse that result. Users with `SALES_REP`
do not trigger CRM Service Request or Workload Actions queries or see their badges.
Identity must contain a valid user (id, name, roles) and token before dependent
requests start. Failed identity validation keeps those requests waiting and retries
with backoff. Reload the page to refresh identity/roles.
The identity token is retained in debug responses and hidden by default; the
explicit sensitive-values toggle reveals the original value.
Opportunity retrieval first calls `requestOpportunitiesSavedSearch` and requires
exactly one valid `isDefault=true` item. Its `savedSearchNumber` becomes
`copiedFrom: queries/<savedSearchNumber>` for every opportunity page. A missing or
ambiguous default blocks the dependent query instead of using a hard-coded value.
After an initial identity failure, recovery opens `https://spa.oracle.com/` in one
inactive auxiliary tab. The top-frame-only `spa-page.js` content script inspects
the page without sending HTTP requests. It rejects error pages, SAML/login forms,
OAuth callbacks and incomplete documents; cookies, HTTP 200 and elapsed time alone
do not establish readiness. After a stable loaded document is verified, identity
is fetched in that tab's same-origin context, at most once per document. A failed
read requires a fresh verified navigation before retrying. Rejected callbacks
restart at the SPA home URL, never at the one-use callback URL. A two-minute
readiness timeout ends the cycle without an additional API probe. The owned tab
and observers are cleaned up on success or failure. Debug metadata records the
page state, wait reason and `spa-tab` execution context; sensitive values remain
hidden by default. Reload the extension after changes to the manifest or script.
Consumer token recovery also handles HTTP 401/403, in addition to HTTP 200 with
invalid JSON. It opens the existing SPA session URL in an inactive temporary tab,
preserves the originating window and Firefox cookie store, and retries within
the same five-attempt limit. Debug entries retain the HTTP status and recovery
reason. The auxiliary tab is closed after valid JSON or exhausted retries.
After a completed product-loading cycle with fresh API results, `resquestCrmServiceRequest` fetches all pages of
`CRMServiceRequest` records with `SVC_PENDING_DELIVERY` and `ORA_SVC_MY_SR_TEAM_SRS`,
using the existing general tokenrelay Bearer. It uses the Fusion `searchResources/latest`
query endpoint, independently of the opportunity query version. Calls appear in
the Service Requests debug category with masked credentials.
After all opportunity pages are displayed, SR and product enrichment start in the
background. Before the first `resquestCrmServiceRequest` page,
`requestSRSearchResources` retrieves CRMServiceRequest searches with the general
Token Relay Bearer. The first item's `savedSearchNumber` supplies SR `copiedFrom`;
this is independent of Opportunity's default-search selection.
SR fetches all pages of `SVC_PENDING_DELIVERY` / `ORA_SVC_MY_SR_TEAM_SRS` records
through Fusion `searchResources/latest`. Each page can update badges while the
remaining pages load. These requests appear in the Service Requests debug category.
After a complete successful SR query (HTTP 200 with valid responses on all pages),
its result is reused for subsequent Reference/product cycles in the current page
session. Refresh all explicitly clears it. Failed pages are retried up to five
@@ -150,12 +248,12 @@ SR snapshots are stored separately by update timestamp so changing Reference doe
not overwrite a previous period's snapshot. Expired snapshots are removed on
subsequent SR cache writes; Refresh all invalidates every snapshot.
No credentials, request headers or debug envelopes are stored in this cache.
Products loaded from cache restore matching activity entries without network calls.
Missing, expired or mismatched entries are not treated as confirmed empty results;
they wait for a real product refresh before a new activity query is allowed.
Empty and entirely failed product cycles do not query activities. In mixed cycles,
only fresh products permit activity API calls on cache misses; an already-loaded
SR result is reused and associated with the new product update.
Matching activity entries can be restored without network calls. Missing, expired
or mismatched entries remain unknown until queried, and cache-miss activity
lookups are allowed even when the products came from cache. A completed SR result
is reused and can be associated with the current product update. Empty or entirely
failed product cycles do not start product-dependent Actions; independently started
SR loading does not depend on a successful product cycle.
Activity lookups do not advance the existing Last update timestamp.
Closing and reopening the modal preserves results and does not
reload products or trigger these lookups. Already-running product requests may
@@ -196,19 +294,33 @@ Workload Actions are queried in the background after the product cycle completes
and only after SR has completed successfully (or was restored/reused). If SR is
unavailable, Actions are not requested and remain unknown rather than absent.
Each failed workload lookup gets up to five total attempts, stopping on success
or when its session is superseded. Cached products still do not trigger network
lookups. Each distinct `RevnId` uses `requestWlActions` with
or when its session is superseded. Valid activity cache entries are reused;
cache misses can trigger lookups for cached products. Each distinct `RevnId` uses `requestWlActions` with
the `WWB-TS` credential in `Wwb-Provider-Authorization`. The Act badge beside SR
aggregates all products: green means an action's `owner` matches the general
tokenrelay response's `principal`; red means no matching owner was found. `Act ?`
means ownership could not be confirmed (including a missing principal). Tooltips
retain all returned actions, regardless of owner. Credentials stay in memory only;
the principal is used to scope persisted activity entries.
retain all returned actions, regardless of owner. The principal scopes persisted
activity entries; this data cache excludes credentials and debug envelopes.
Queries are shared per product cycle (up to four concurrently). Reference changes
that load products from cache do not refresh activities.
reuse matching activity data or load missing data as needed.
Only the first page is requested for now; `hasNext` is indicated in the tooltip.
The debug panel groups these requests under Actions, with credentials masked.
## Manage Time
Eligible users can open **Manage Time** once the SR session is ready. It restores
the selected reference date, moves by seven days and preserves local week drafts
while navigating within the modal. Service Request, Activity and Task Type use
searchable dependent selections; Service and Non Service rows use their respective
task types. Duplicate SR selections are rejected with row feedback.
Daily hours must be numeric and between 0 and 24 inclusive. Save creates new
entries, updates changed saved entries and applies queued deletions. Deleting an
unsaved row is local; deleting a saved row waits for Save. Partial failures retain
unfinished work without repeating confirmed writes. Loading uses skeletons and
errors expose retry. The feature is unavailable for SALES_REP.
## Build
Use Node.js:
@@ -227,14 +339,12 @@ after updating; clear historical extension errors to check for new failures.
Do not edit files under `dist` directly. Both targets use Manifest V3, with a
Chromium service worker and Firefox background scripts. The build composes classic
factory modules from `scripts/sources.cjs` into one isolated IIFE per execution
context. It serializes `src/content/styles/redwood.css` followed by the scoped
`src/features/debug/styles.css`, `src/features/opportunities/styles.css`,
`src/features/install-base/styles.css`, `src/features/service-requests/styles.css`
`src/features/consumption/plan-detail.css` and `src/features/ratecard/styles.css` at
build time. There is no
context. CSS is serialized at build time in the order defined by
`scripts/compose.cjs`, including feature styles followed by shared table and modal
overrides in `src/content/styles/tables.css` and `dialogs.css`. There is no
runtime module loader, browser-side eval, ES-module requirement or new dependency.
## Tests and Maintenance
## Customer Insights
Installed Base and Service Requests include a search input above their tables.
Search filters loaded records across every displayed column, including raw and
@@ -256,8 +366,12 @@ It uses the existing OCICONS-TS `uiaasHeader.value` in `G2m-Authorization` and i
logged in the Consumption debug category with the existing credential masking.
The eleven requested fields are displayed, including percentage discounts and
USD prices with at least four decimal places. Search includes raw and formatted
values; prices, quantities, percentages and dates sort by value. Only the drawer
table scrolls, horizontally when its available width is below 1100px.
values; prices, quantities, percentages and dates sort by value. An **Order Number**
multiselect beside Search groups distinct order numbers and supports selecting all
or several orders. Clicking one order while all are selected isolates that order.
Order selections and text search combine locally.
Shared table styles remove the old fixed minimum width, tighten cell spacing and
allow multiword headers to wrap at word boundaries so the table fits the drawer.
Closing the drawer, changing plans or closing Cloud Consumption discards late UI
updates. Concurrent requests for the same plan are shared; reopening after completion
fetches again. Errors remain local. No new persisted cache, permissions, token
@@ -296,7 +410,8 @@ and formats status as a badge and dates using the existing date formatter.
Debug contains a Service Requests category; credential headers are masked before
delivery to content. This feature adds no permissions or authentication retries.
The Installed Base action sits beside customer consumption below Customer. It
The Installed Base action sits beside customer consumption below Customer. The
customer appears in its modal header rather than a repeated Customer column. It
opens a skeleton dialog, then calls `requestAccountSummary` with ACCP-TS and uses
the returned `registryId` in `requestInstallBase`. The latter uses the SAIB-TS
`uiaasHeader.label` and `uiaasHeader.value`, and loads pages of 25 until `hasMore`
@@ -306,15 +421,23 @@ headers in this flow are masked before delivery to the content script. No new
cache, permissions or session recovery logic is introduced. Amounts use two
decimal places without inferring a currency from the separate LC fields.
The opportunities table places product counts below Name and the customer
consumption action below Customer, without a separate Actions column. Compact
columns retain their values; Name and Customer share the remaining width while
Owner is fixed at 132px. Both text cells reserve at least two text lines, and
their controls stay anchored 10px above the row bottom, including while products
load. Longer text can grow the row without overlapping controls. Horizontal
scrolling is reserved for table containers below
1120px. The scoped table stylesheet does not change product-table column widths
or the existing light/dark colors.
The opportunities table places product counts and badges below Name and customer
actions below Customer. Their spacing is coordinated; longer names can grow the
row without overlapping controls. Shared table and modal styles provide consistent
titles, typography, borders, padding and loading skeletons in both themes.
## Debug Requests
Request records are persisted in page localStorage under a tab-specific namespace.
Closing the main modal preserves them. The reset occurs immediately before the
first `requestOpportunities` page of a new query; subsequent pages preserve that
query's history. Requests logged before that reset, including its saved-search
lookup, are removed by the same boundary.
The hidden debug panel does not prebuild request details. Opening it loads the
summaries and expanding an entry constructs its details. Storage is updated in
batches of changed records; quota or access failures retain the session's records
in memory with explanatory feedback rather than interrupting API requests.
Product HTTP requests appear in Debug Requests (Ctrl+Shift+D) as soon as they start.
Each entry identifies the opportunity and page offset, stays `running` while
@@ -325,7 +448,8 @@ tab/frame; this change adds no timeout, retry or authentication behavior.
Debug Requests opens as a full-page view inside the extension using Ctrl+Shift+D, without
opening a browser tab or entering native browser fullscreen. Close, Escape or Ctrl+Shift+D
returns to the underlying view. Header/category navigation remains above the
returns to the underlying view. Debug stays above other extension modals.
Header/category navigation remains above the
scrollable timeline; expanded details use two columns on wide screens and one on
narrow screens. Theme-specific colors distinguish success, failure, running,
HTTP methods/statuses, request/response sections and JSON tokens. Highlighting
@@ -336,15 +460,20 @@ are captured in full, preserving indentation, highlighting and sensitive-field
masking. Original sensitive values are retained for explicit reveal, including
identity, tokenrelay and catalog tokens. Already incomplete
or invalid JSON remains plain text. The visibility toggle retains its existing
behavior. Focus, expansion and timeline scroll position survive log updates.
behavior. This is display masking, not encryption or guaranteed redaction at rest:
captured records can retain original sensitive values in localStorage. Focus,
expansion and timeline scroll position survive log updates.
Debug tabs group Authentication, Opportunities, Products, AI, Consumption, Installed Base and Service Requests
Debug tabs group Authentication, Opportunities, Contacts, Products, Consumption,
Installed Base, Service Requests and Actions
requests; unknown request types appear under Other. Only categories with entries
are shown. All preserves the complete timeline. Every tab sorts by request start
time, retaining original request IDs and showing start times in collapsed rows.
Counts and running states update without changing the selected category or
collapsing expanded details. Use arrow keys, Home and End to navigate the tabs.
## Tests and Maintenance
```bash
npm test
npm run check
@@ -354,21 +483,25 @@ Tests use built-in Node.js modules and synthetic credentials, without real Fusio
requests. The characterization suite verifies manifests, source/output equality,
fiscal dates, payloads, preferences, pagination, cache/progress and both browser API
styles. Architecture tests enforce source inclusion, explicit factory dependencies,
isolated state and unchanged CSS. These complement consumption, session recovery
and AI suites. `check` also fails if generated packages are stale.
isolated state and unchanged CSS. These complement consumption, session recovery,
organization-chart and Manage Time suites. `check` also fails if generated packages are stale.
There is no separate lint configuration or runtime dependency installation.
See [architecture and contracts](docs/architecture.md) and the
[incremental refactoring record](docs/refactoring.md) for execution boundaries,
authentication, storage, known risks and verification limits.
Mock-based tests do not replace testing the installed extension in each browser.
Calendar week positioning, responsive tables, modal dragging and print pagination
still need visual checks in installed browsers; synthetic tests alone do not
certify those layouts or live Oracle authentication.
Without npm, run `node scripts/build.mjs`, `node scripts/test.cjs` and
`node scripts/build.mjs --check`. Development checks were run with Node 24.19.0.
`node scripts/build.mjs --check`. The documentation-baseline checks use Node 22.15.0;
the build has no runtime dependency installation step.
For a local UI smoke test, run `node scripts/preview.mjs` and open the loopback URL
printed by the command. The fixture uses synthetic opportunities, tokens, products,
AI text and consumption; unexpected requests fail closed. It does not authenticate
and consumption data; unexpected requests fail closed. It does not authenticate
against Fusion or read real extension storage. `/consumption-trend.html` provides
a focused chart fixture. Stop the server with Ctrl+C when finished.
@@ -394,4 +527,24 @@ a focused chart fixture. Stop the server with Ctrl+C when finished.
When it finds the `#yourapps_groupNode_sales` group, the extension adds the tile before the `.flat-grid-cell.flat-grid-cell-addicon` item. Clicking the tile opens the opportunities interface.
With the Opportunities Extension modal open, press Ctrl+Shift+F to choose a browser bookmarks folder and save the extension shortcut.
Opening the saved shortcut ending in `#opportunities-extension` also opens the
modal without clicking the Sales group. Its intent is captured before Fusion can
rewrite the URL, then consumed once when the extension is ready.
## Favorites, Modal Interaction and Printing
With the main modal open, **Ctrl+Shift+F** opens a searchable browser bookmark
folder picker. Choose the destination and Save; the extension uses the bookmarks
API rather than the browser's native Add Favorite dialog. If the shortcut is not
bookmarked, a prominent hint appears for 20 seconds and disappears immediately
after saving. **Last update** remains the first header badge.
Non-full-page modals can be dragged from any noninteractive area of their title
bar. Up to 70% of their width and height can move beyond the viewport; title-bar
buttons keep their normal actions. Full-page views remain fixed.
The print icon beside **Booking** and **Workload** opens a report containing the
current filters, counts, stage amounts and opportunity list. Unrestricted Customer
and Owner selections are labeled **All customers** and **All owners**. The report
requests A4 landscape with page margins and supports the browser's Print / Save PDF
dialog. Browser print settings can override the requested orientation and paper.