@globalfishingwatch/skills 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +89 -0
  2. package/dist/decode-url/SKILL.md +51 -0
  3. package/dist/decode-url/index.js +20339 -0
  4. package/dist/decode-url/scripts/decode-url.mjs +18 -0
  5. package/dist/decode-url/scripts/register-gfw-resolver.mjs +24 -0
  6. package/dist/encode-url/MAINTENANCE.md +53 -0
  7. package/dist/encode-url/SKILL.md +135 -0
  8. package/dist/encode-url/index.js +20211 -0
  9. package/dist/encode-url/references/areas.json +1401 -0
  10. package/dist/encode-url/references/examples-conversations.md +113 -0
  11. package/dist/encode-url/references/examples.md +234 -0
  12. package/dist/encode-url/references/filters.md +67 -0
  13. package/dist/encode-url/references/highlighted-workspaces.md +65 -0
  14. package/dist/encode-url/references/layers.md +96 -0
  15. package/dist/encode-url/references/ports.json +27507 -0
  16. package/dist/encode-url/references/query-params.md +114 -0
  17. package/dist/encode-url/references/routes.md +24 -0
  18. package/dist/encode-url/scripts/encode-url.mjs +26 -0
  19. package/dist/encode-url/scripts/register-gfw-resolver.mjs +24 -0
  20. package/dist/index.js +20514 -0
  21. package/dist/package.json +35 -0
  22. package/package.json +35 -0
  23. package/src/decode-url/SKILL.md +51 -0
  24. package/src/decode-url/decode.ts +87 -0
  25. package/src/decode-url/index.ts +4 -0
  26. package/src/decode-url/scripts/decode-url.mjs +18 -0
  27. package/src/decode-url/scripts/register-gfw-resolver.mjs +24 -0
  28. package/src/decode-url/workspaces.ts +34 -0
  29. package/src/encode-url/MAINTENANCE.md +53 -0
  30. package/src/encode-url/SKILL.md +135 -0
  31. package/src/encode-url/config.ts +8 -0
  32. package/src/encode-url/dictionary.ts +305 -0
  33. package/src/encode-url/encode.ts +79 -0
  34. package/src/encode-url/index.ts +4 -0
  35. package/src/encode-url/references/areas.json +1401 -0
  36. package/src/encode-url/references/examples-conversations.md +113 -0
  37. package/src/encode-url/references/examples.md +234 -0
  38. package/src/encode-url/references/filters.md +67 -0
  39. package/src/encode-url/references/highlighted-workspaces.md +65 -0
  40. package/src/encode-url/references/layers.md +96 -0
  41. package/src/encode-url/references/ports.json +27507 -0
  42. package/src/encode-url/references/query-params.md +114 -0
  43. package/src/encode-url/references/routes.md +24 -0
  44. package/src/encode-url/routes.ts +166 -0
  45. package/src/encode-url/scripts/encode-url.mjs +26 -0
  46. package/src/encode-url/scripts/register-gfw-resolver.mjs +24 -0
  47. package/src/index.ts +6 -0
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import './register-gfw-resolver.mjs'
3
+
4
+ const url = process.argv[2]
5
+ if (!url) {
6
+ console.error(`Usage: node decode-url.mjs '<map url or path>'`)
7
+ process.exit(1)
8
+ }
9
+
10
+ const { decodeMapUrl } = await import('@globalfishingwatch/skills/decode-url')
11
+
12
+ try {
13
+ const result = decodeMapUrl(url)
14
+ console.log(JSON.stringify(result, null, 2))
15
+ } catch (error) {
16
+ console.error(error.message)
17
+ process.exit(1)
18
+ }
@@ -0,0 +1,24 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { registerHooks } from 'node:module'
3
+ import path from 'node:path'
4
+ import { fileURLToPath, pathToFileURL } from 'node:url'
5
+
6
+ // Each skill ships its own self-contained bundle next to it. Resolve it from either layout:
7
+ // - dist/<skill>/scripts (dist output, this skill copied standalone) -> ../index.js
8
+ // - <skill>/scripts (monorepo source, unbuilt) -> ../../dist/<skill>/index.js
9
+ const skillDir = fileURLToPath(new URL('..', import.meta.url))
10
+ const skillName = path.basename(skillDir)
11
+ const SPECIFIER = `@globalfishingwatch/skills/${skillName}`
12
+ const BUNDLE = [
13
+ path.join(skillDir, 'index.js'),
14
+ path.join(skillDir, '..', '..', 'dist', skillName, 'index.js'),
15
+ ].find(existsSync)
16
+
17
+ registerHooks({
18
+ resolve(specifier, context, nextResolve) {
19
+ if (specifier === SPECIFIER && BUNDLE) {
20
+ return { url: pathToFileURL(BUNDLE).href, format: 'module', shortCircuit: true }
21
+ }
22
+ return nextResolve(specifier, context)
23
+ },
24
+ })
@@ -0,0 +1,53 @@
1
+ # Maintaining the encode-url skill
2
+
3
+ Not loaded at skill runtime — this is the prompt/checklist for keeping the skill docs in sync with the app. Run it after changes to the fishing-map state types, URL encoding, or dataview/layer config.
4
+
5
+ ## Update prompt
6
+
7
+ Paste (or point Claude at) the following:
8
+
9
+ > Sync the `encode-url` skill docs (`libs/skills/src/encode-url/`) with the app source. Requirements:
10
+ >
11
+ > 1. **Diff params against the documented types.** The four source-of-truth files carry JSDoc on every state field, all under `apps/fishing-map/`: `types/index.ts` (`WorkspaceState`/`AppState`/`QueryParams`), `features/reports/reports.types.ts`, `features/vessel/vessel.types.ts`, `features/search/search.types.ts`. Compare against `references/query-params.md`; add new intent-useful params, remove deleted ones.
12
+ > 2. **Diff every enumerated value.** The "Value sources (for update checks)" table in `libs/skills/src/encode-url/MAINTENANCE.md` maps each param to the exact symbol and file its values were copied from. Read each symbol; diff its values against the doc tables in `references/query-params.md`; fix drift. Add a row for any newly documented enum param.
13
+ > 3. **Coverage policy: curated, not exhaustive.** Document params a URL-building intent can actually use. Internal/auto-generated params stay out of the tables and go in the "never set" footnote near the top of `references/query-params.md` — that footnote is the authoritative list; keep it current instead of documenting those params.
14
+ > 4. **URL param names, not state field names.** A `state` param must match a key in `PARAMS_TO_ABBREVIATED` (`libs/dataviews-client/src/url-workspace/url-workspace.ts`) or serialize under its full name. Known trap: URL param `reportResultsPerPage` vs type field `reportVesselResultsPerPage`. Advanced search fields (`transmissionDateFrom/To`, from `ADVANCED_SEARCH_QUERY_FIELDS` in `libs/api-client/src/utils/search.ts`) are a different namespace from `firstTransmissionDate`/`lastTransmissionDate` (`fTD`/`lTD`).
15
+ > 5. **Layer/instance ids** come from `apps/fishing-map/config/src/workspaces.ts` constants and must resolve in the encoder dictionary `libs/skills/src/encode-url/dictionary.ts` (known trap: the FAO context instance is `context-layer-fao-areas`, not `context-layer-fao`).
16
+ > 6. **Defaults** come from `DEFAULT_WORKSPACE` (`apps/fishing-map/data/config.ts`) and `DEFAULT_REPORT_STATE` (`apps/fishing-map/features/reports/reports.config.ts`) — mark them `(default)` in the tables.
17
+ > 7. **Examples live in two files.** `references/examples.md` holds input recipes; `references/examples-conversations.md` holds conversation transcripts with expected output URLs. Update transcripts if encoder output changes.
18
+ > 8. **Verify, don't trust.** `pnpm nx dist skills`, then run `node scripts/encode-url.mjs` (node >= 23) with inputs exercising every param you touched; confirm expected abbreviations/pass-through names in the output path. Round-trip against the transcripts in examples-conversations.md. Finish with `pnpm prettier --write` on every markdown file touched.
19
+
20
+ ## Value sources (for update checks)
21
+
22
+ Every enumerated value in `references/query-params.md` is copied from one of these symbols. To check for updates: read each symbol and diff its values against the tables in that file. This table lives here (not in the reference) to keep skill-runtime token cost down.
23
+
24
+ | Param(s) | Symbol | File |
25
+ | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- |
26
+ | `timebarVisualisation` | `TimebarVisualisations` enum | `apps/fishing-map/types/index.ts` |
27
+ | `timebarGraph` | `TimebarGraphs` enum | `apps/fishing-map/types/index.ts` |
28
+ | `visibleEvents` | `EventTypes` enum (app toggles use `Gaps = 'gaps'`, not `Gap`) | `libs/api-types/src/events.ts` |
29
+ | `activityVisualizationMode`, `detectionsVisualizationMode` | `FOURWINGS_VISUALIZATION_MODES` | `libs/deck-layers/src/layers/fourwings/fourwings.config.ts` |
30
+ | `environmentVisualizationMode` | `HEATMAP_ID`, `HEATMAP_LOW_RES_ID` | `libs/deck-layers/src/layers/fourwings/fourwings.config.ts` |
31
+ | `vesselGroupsVisualizationMode` | `FOOTPRINT_ID`, `FOOTPRINT_HIGH_RES_ID` | `libs/deck-layers/src/layers/fourwings/fourwings.config.ts` |
32
+ | `vesselsColorBy` | `VesselsColorByProperty` | `libs/deck-layers/src/layers/vessel/vessel.config.ts` |
33
+ | `mapDrawing` | `DrawFeatureType` | `libs/deck-layers/src/layers/draw/DrawLayer.ts` |
34
+ | `colorRamp`/`color` palette | `FILL_COLOR_BAR_OPTIONS` | `libs/ui-components/src/color-bar/color-bar-options.ts` |
35
+ | `reportCategory` | `ReportCategory` enum | `apps/fishing-map/features/reports/reports.types.ts` |
36
+ | `reportActivitySubCategory`, `reportDetectionsSubCategory`, `reportVesselsSubCategory` | `ReportActivitySubCategory`, `ReportDetectionsSubCategory`, `ReportVesselsSubCategory` types (built on `DatasetSubCategory` from `libs/api-types/src/datasets.ts`) | `apps/fishing-map/features/reports/reports.types.ts` |
37
+ | `reportEventsSubCategory` | `EventType` (= `EventTypes` values) | `libs/api-types/src/events.ts` |
38
+ | `reportActivityGraph` | `REPORT_ACTIVITY_GRAPH_*` consts | `apps/fishing-map/features/reports/reports.config.ts` |
39
+ | `reportEventsGraph` | `REPORT_EVENTS_GRAPH_*` consts | `apps/fishing-map/features/reports/reports.config.ts` |
40
+ | `reportVesselGraph` | `REPORT_VESSELS_GRAPH_*` consts | `apps/fishing-map/features/reports/reports.config.ts` |
41
+ | `reportVesselOrderProperty`/`Direction` | `REPORT_VESSEL_ORDER_PROPERTIES`, `REPORT_VESSEL_ORDER_DIRECTIONS` | `apps/fishing-map/features/reports/reports.types.ts` |
42
+ | `reportBufferUnit`, `reportBufferOperation` | `BUFFER_UNITS`, `BUFFER_OPERATIONS` | `apps/fishing-map/types/index.ts` |
43
+ | `vesselSection`, `vesselArea`, `vesselRelated`, `vesselActivityMode` | `VESSEL_SECTIONS`, `VESSEL_AREA_SUBSECTIONS`, `VESSEL_RELATED_SUBSECTIONS`, `VESSEL_PROFILE_ACTIVITY_MODES` | `apps/fishing-map/features/vessel/vessel.types.ts` |
44
+ | advanced search fields | `ADVANCED_SEARCH_QUERY_FIELDS` | `libs/api-client/src/utils/search.ts` |
45
+ | `userTab` | `UserTab` enum | `apps/fishing-map/types/index.ts` |
46
+ | URL param abbreviations | `PARAMS_TO_ABBREVIATED` (params NOT listed there serialize under their full name) | `libs/dataviews-client/src/url-workspace/url-workspace.ts` |
47
+
48
+ ## Doc structure invariants
49
+
50
+ - Each reference file's header names its source-of-truth files.
51
+ - The "Value sources" table above stays current — symbol + file, no line numbers (they rot). It lives in MAINTENANCE.md, not in the references, to avoid skill-runtime token cost.
52
+ - SKILL.md points agents at the typed JSDoc for params not covered in query-params.md.
53
+ - Format touched markdown with `pnpm prettier --write`.
@@ -0,0 +1,135 @@
1
+ ---
2
+ name: encode-url
3
+ description: Build a GFW map URL (and TanStack Router navigation config) that matches exactly what the fishing-map app generates. Use when the user wants to see, compare or review ocean activity (fishing effort, vessel presence, detections, encounters, loitering, port visits, environment layers), open a report over an area or port, open a vessel profile, or search vessels — and you need to produce the link or in-app navigation for it.
4
+ ---
5
+
6
+ # Encode GFW map URL
7
+
8
+ Turn a navigation intent into `{ navigation, path }`:
9
+
10
+ - `navigation`: TanStack Router config (`to`, `params`, `search`) — use to navigate from inside the map app.
11
+ - `path`: path + encoded query string (e.g. `/map/fishing-activity/default-public?...`) — for external navigation prepend the origin `https://globalfishingwatch.org`.
12
+
13
+ Never hand-build the query string: params are abbreviated (`dataviewInstances`→`dvIn`, `config`→`cfg`, `visible`→`vis`…) and repeated values are tokenized (`~0`). The script does this with the app's own encoder.
14
+
15
+ ## Workflow
16
+
17
+ 1. **Pick the route type** from the user intent (details in [references/routes.md](references/routes.md)):
18
+ - browse/compare activity on the map → `workspace`
19
+ - a marine protected area / region with a curated workspace (Galapagos, Palau, Fiji, Mediterranean…) or a global curated report (activity, dark vessel detections, events, deep sea mining) → ids in [references/highlighted-workspaces.md](references/highlighted-workspaces.md) only when marine manager is mentioned
20
+ - aggregated report over an area (EEZ, FAO, RFMO) → `report` (needs `datasetId` + `areaId`)
21
+
22
+ - An area report must ALSO include the matching **context layer** as a visible instance so the area outline renders: `context-layer-eez` / `context-layer-fao-areas` / `context-layer-rfmo` / `context-layer-mpa` (pick by area type; dataviewId in [references/layers.md](references/layers.md)), `{ "config": { "visible": true } }`. Missing it drops the area boundary.
23
+
24
+ - Multiple areas in one report → pass `datasetId`/`areaId` as comma-joined lists of equal length (one datasetId per areaId), e.g. `"datasetId":"public-fao-major,public-fao-major","areaId":"41,87"` → path `/report/public-fao-major%2Cpublic-fao-major/41%2C87`. Adding an area to an existing report = append its dataset+id to both. Re-center viewport (`latitude`/`longitude`/`zoom`) on the combined area set rather than keeping the source values.
25
+ - port activity profile → `ports-report` (needs `portId`)
26
+ - vessel profile → `vessel` (needs `vesselId`)
27
+ - find a vessel by name/MMSI/IMO/owner → `vessel-search`
28
+ - user account / saved workspaces → `user`
29
+
30
+ - marine-manager workspaces index → static path `/map/marine-manager` (no state/query at all)
31
+ - If both a `workspace` (browse on the map) and a `report` (aggregated stats over an area) could serve the intent, return both results — the map view and the report — so the user picks.
32
+
33
+ 2. **Pick layers** as `state.dataviewInstances` using the instance ids in [references/layers.md](references/layers.md). Rules:
34
+ - A layer the user wants: `{ "id": "<instance-id>", "config": { "visible": true, ... } }`.
35
+ - Default-workspace layers the user does NOT want (`ais`, `vms` are visible by default) must be included with `"config": { "visible": false }` — otherwise the app shows them anyway. This `ais`/`vms` default is for `default-public` ONLY — a curated workspace (see [highlighted-workspaces.md](references/highlighted-workspaces.md)) bakes in its own different default ids and must be checked there (or decoded live) before assuming.
36
+ - Generic "fishing" intent → show AIS fishing effort (`ais`) and hide `vms` with `"config": { "visible": false }` unless the user specifically wants VMS. Only keep `vms` visible when asked for VMS/national-fleet data.
37
+ - Layers not in the default workspace need `dataviewId` too (see layers.md). Versioned dataview slugs take the literal `{PIPE_DATASET_VERSION}` token (e.g. `sar-v-{PIPE_DATASET_VERSION}`) — the script resolves it from the `PIPE_DATASET_VERSION` env variable so URLs always target the latest dataset pipeline version.
38
+ - Multiple visible instances of the same category (e.g. two fishing-effort layers filtered per flag): give each a distinct color so they're visually separable. `color` and `colorRamp` are one choice — set both to a matching palette entry (ramp id + its paired hex, table in query-params.md); don't repeat a ramp within the category.
39
+ 3. **Set filters** in `config.filters` — valid filter ids and enum values per dataset are in [references/filters.md](references/filters.md). Flags are ISO3 codes (`ESP`, `FRA`, `CHN`).
40
+ - Multiple flags → one instance per flag (each with its own `filters.flag` + distinct color/colorRamp per the rule above), so they compare side by side. Only combine flags into a single instance's `filters.flag` array when the user asks for the combined/total (e.g. "Spain and France together").
41
+ 4. **Set the rest of the state** ([references/query-params.md](references/query-params.md)): `start`/`end` (ISO datetimes), `latitude`/`longitude`/`zoom`, and `timebarVisualisation` (`heatmap` for activity, `heatmapDetections` for detections, `events` for encounters/loitering/port visits).
42
+
43
+ - **Relative time periods**: compute `start`/`end` with plain date math from the current datetime (e.g. "last year" = now minus 1 year → now). The encoder snaps both to the map's interval resolution automatically
44
+ - **Report viewport = don't set it.** For an area report the tools is going to calculate it dinamycally.
45
+
46
+ 5. **Look up ids when needed**: grep [references/areas.json](references/areas.json) for EEZ/FAO/RFMO area ids and [references/ports.json](references/ports.json) for port ids (large file — always grep by name, never read it whole).
47
+ - Region not in areas.json → no area `report` possible (needs a real `areaId`). Options: (a) a `workspace` framed by viewport — set `latitude`/`longitude`/`zoom` to your best estimate of the region's center; (b) a **global report** for whole-world stats. Global report = `route.type` `report` with NO `datasetId`/`areaId` (→ `/reports/default-public/report`); set viewport to world (`latitude: 0`, `longitude: 0`, `zoom: ~0.8`, not `0`). A global/whole-world report keeps ALL default-workspace layers VISIBLE (`ais`, `vms`, `presence`, `sar`, `sentinel2`, `viirs`, `encounters`, `loitering`, `port-visits`) — do not hide them and do not include only the one the user named. Use the global report when the user wants aggregated numbers but no specific area matches.
48
+ 6. **Run the script**:
49
+
50
+ ```bash
51
+ node scripts/encode-url.mjs '{"route":{"type":"report","datasetId":"public-eez-areas","areaId":"5682"},"state":{...}}'
52
+ # or pipe: echo '<json>' | node scripts/encode-url.mjs
53
+ ```
54
+
55
+ Input shape:
56
+
57
+ ```json
58
+ {
59
+ "route": {
60
+ "type": "workspace|report|vessel|vessel-search|ports-report|vessel-group-report|user",
61
+ "category": "fishing-activity",
62
+ "workspaceId": "default-public",
63
+ "datasetId": "...",
64
+ "areaId": "...",
65
+ "vesselId": "...",
66
+ "portId": "...",
67
+ "vesselGroupId": "..."
68
+ },
69
+ "state": {
70
+ "dataviewInstances": [],
71
+ "start": "...",
72
+ "end": "...",
73
+ "latitude": 0,
74
+ "longitude": 0,
75
+ "zoom": 1,
76
+ "timebarVisualisation": "events"
77
+ }
78
+ }
79
+ ```
80
+
81
+ `category`/`workspaceId` default to `fishing-activity`/`default-public` when omitted.
82
+
83
+ Worked examples of real intents → inputs: [references/examples.md](references/examples.md). Real conversation transcripts with expected output URLs: [references/examples-conversations.md](references/examples-conversations.md).
84
+
85
+ A state param not covered in query-params.md may still exist — the app state types carry full JSDoc: `apps/fishing-map/types/index.ts`, `features/reports/reports.types.ts`, `features/vessel/vessel.types.ts`, `features/search/search.types.ts`.
86
+
87
+ ## Working from an existing map URL
88
+
89
+ When the input gives a **current map URL** (a follow-up like "and by flag?", "what about other gear types?", "are there non-fishing vessels?"), decode it into the full state first and treat that as the base. Make the **smallest change** that answers the intent, then re-run the encoder on the whole state. Output = input state minus nothing, plus/edited only the user's delta.
90
+
91
+ - **Preserve everything unmentioned, verbatim.** Params commonly dropped by accident that MUST carry over:
92
+ - viewport: `latitude`, `longitude`, `zoom` (keep the exact input values; don't recompute).
93
+ - time: `start`, `end` (change only when the user gives a new period).
94
+ - report buffer: `rBO`/`rBU`/`rBV` (`reportBufferOperation`/`reportBufferUnit`/`reportBufferValue`).
95
+ - temporal filters: `fTD`/`lTD` (even when empty — emit `fTD=&lTD=`).
96
+ - every existing `dataviewInstances` entry at its current visibility (id, category, dvId, color, colorRamp, filters). Do NOT hide, drop, reorder, or strip filters from a layer the user didn't ask to change.
97
+ - **Add, don't replace.** "Also show / are there also X" ADDS a new instance for X while leaving current layers visible — not toggle the current one off.
98
+ - **Minimal filter change, no enumeration.** "Other / the rest / remaining / non-<current>" relative to an applied filter means **clear/broaden that filter** (set it to `''` / remove it) on the existing layer. Never enumerate the complement as an explicit value list, and never fabricate a new layer to hold it.
99
+
100
+ - **A country/region constraint is an area report, not just a flag filter.** "only in <country>", "restricted to <region>" → convert the route to a `report` over that country's EEZ (grep [references/areas.json](references/areas.json) for its `datasetId`/`areaId`), keeping all existing layers/filters.
101
+ - **Re-encode, never string-edit.** Feed the full decoded+modified state through `scripts/encode-url.mjs`; hand-patching the query string leaves stale/duplicated params (e.g. a stale `zoom`) and skips normalization.
102
+
103
+ Requires node >= 23 (uses `module.registerHooks`). In the monorepo run `pnpm nx dist skills` once so `dist/` exists.
104
+
105
+ ## Getting it right — priority rules (read first)
106
+
107
+ These exist because the same mistakes recur. They override any instinct to hand-build.
108
+
109
+ ### 1. Preserve on follow-ups — and the global-report exception is narrow
110
+
111
+ - Carry over every unmentioned param verbatim: viewport, `start`/`end`, `rBO`/`rBU`/`rBV`, `fTD=`/`lTD=`, and every existing layer at its current visibility/filters.
112
+
113
+ ### 2. Filter-refinement follow-ups modify the existing layer in place — do NOT add a new one
114
+
115
+ - Questions like "any passenger boats?", "squid jiggers?", "only trawlers?" refine an EXISTING layer: set that filter on the layer already present, keep its id/color/category. Do NOT create a second layer for the filtered subset.
116
+ - Reserve "add a new instance" for genuinely additive intents that introduce a DIFFERENT category or data source ("also show detections", "add SAR").
117
+ - "Other / the rest / remaining / non-<current>" = clear/broaden the existing filter to empty; never enumerate the complement.
118
+
119
+ ### 3. Clearing a filter emits it empty — never drop the key
120
+
121
+ - To broaden/clear a filter, output it as empty (`geartype=`), not by omitting it. An omitted key scores as `None` and fails; the app expects the empty value. This is a persistent, repeated loss.
122
+
123
+ ### 4. AIS fishing-effort carries a default `distance_from_port_km=3`
124
+
125
+ - Every AIS apparent-fishing-effort layer (`ais` and `fishing-effort-ais__*`) includes `filters.distance_from_port_km=3` unless the user overrides it. Include it even when the user only mentioned a gear/flag filter.
126
+
127
+ ### 5. Multi-country fishing intent = WORKSPACE with one layer per flag, not a multi-area report
128
+
129
+ - "Fishing of Peru, Argentina, Brazil and Chile" → a `workspace` with one fishing-effort-ais layer per flag, each with `filters.flag` and a distinct color/colorRamp
130
+
131
+ ### 6. Curated workspaces have their own defaults, and their own EEZ may have a national dataset
132
+
133
+ - Don't carry the `default-public` assumption (`ais`/`vms` visible by default) into a curated workspace — check its real default ids in [highlighted-workspaces.md](references/highlighted-workspaces.md) (or decode the bare workspace URL) and hide the ones the user doesn't want.
134
+ - If the workspace's EEZ has a national fishing-effort dataset (e.g. Ecuador for Galapagos, Brazil), prefer it over plain global `ais` for that flag/region — pattern in [layers.md](references/layers.md#national-fishing-effort-datasets).
135
+ - Set the viewport to the region's center for a curated `workspace` route (not `0,0`) — the "don't set viewport" rule only applies to `report` routes.