Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions .rpiv/guidance/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Project Overview

Volto (React) add-on for Climate-ADAPT — the European Climate Adaptation Platform. Provides custom blocks, views, widgets, search engines, theme overrides, and core component shadowing for a Plone 6 + Volto 17 backend.

## Architecture

```
volto-cca-policy/
├── src/index.js # Add-on entry — applyConfig composes all installers
├── src/components/ # Custom UI: blocks, views, widgets, search results
├── src/customizations/ # Shadowed Volto/EEA/core components (must have README.md)
├── src/search/ # Elasticsearch search engine configurations (7 engines)
├── src/store/ # Redux reducers, actions, middleware
├── src/helpers/ # Shared utilities (.js) and React components (.jsx)
├── src/icons/ # Custom SVG icon registry
├── theme/ # Semantic UI LESS theme (EEA design system base)
├── locales/ # 27 EU language translation catalogs
├── cypress/ # End-to-end acceptance tests
├── jest-addon.config.js # Jest configuration with coverage path resolution
└── package.json # Addon dependencies and EEA addon chain
```

**Config flow:** `src/index.js` exports `applyConfig(config)` → composes `installBlocks`, `installSearchEngine`, `installStore` → each mutates `config` and returns it → Volto merges at build time.

## Commands

| Command | What it does |
|---|---|
| `make start` | Start dev environment (Docker Compose: frontend + backend) |
| `make test` | Run Jest tests in Docker |
| `make lint` | ESLint check |
| `make lint-fix` | Auto-fix ESLint issues |
| `make i18n` | Extract translation strings from JSX |
| `make cypress-open` | Open Cypress interactive test runner |
| `make cypress-run` | Run Cypress tests headlessly |

## Business Context

Climate-ADAPT serves the European Commission and EEA with climate adaptation data, tools, and knowledge resources across 27 EU languages. The site has three main areas: the main Climate-ADAPT portal, the Health Observatory, and the EU Mission on Adaptation sub-portal.

<important if="you are adding a new block to this layer">
- See `.rpiv/guidance/src/components/architecture.md` — Block Installer Pattern & Adding a New Block
</important>

<important if="you are shadowing a core Volto or EEA addon component">
- See `.rpiv/guidance/src/customizations/architecture.md` — Shadowing Pattern
- Every shadowed component MUST have a README.md explaining the modification
</important>

<important if="you are adding a new search engine or modifying search configuration">
- See `.rpiv/guidance/src/search/architecture.md` — Search Engine Config Pattern
</important>

<important if="you are adding Redux state (reducers, actions, middleware)">
- See `.rpiv/guidance/src/store/architecture.md` — Reducer & Action Creator Patterns
</important>

<important if="you are working with theme styles or Semantic UI overrides">
- See `.rpiv/guidance/theme/architecture.md` — Theme Override Pattern
- Block-specific styles go in the block's own `styles.less`, NOT in the global theme
</important>

<important if="you are writing or modifying tests">
- Unit: Jest + React Testing Library, test files as `.test.jsx` co-located with components
- E2E: Cypress 13, run via `make cypress-open` or `make cypress-run`
- Jest config: `jest-addon.config.js` with module mapping for `@eeacms/*` and `@plone/volto`
- Snapshots in `__snapshots__/` directories; update with `make test-update`
</important>

<important if="you are working with internationalization or translations">
- 27 EU languages in `locales/` (one directory per language code)
- Extract strings with `make i18n` after modifying JSX with `FormattedMessage` or `defineMessages`
- Use `react-intl` — `defineMessages` for message definitions, `FormattedMessage` / `useIntl` in components
- String `id` is the English key; `defaultMessage` is the English fallback
</important>

<important if="you are adding server-side rendering (SSR) middleware">
- SSR express middleware in `src/express-middleware.js` — proxies `@@` views (case studies map, country metadata, translation)
- Guard server-only code with `if (__SERVER__)` checks
- The middleware is conditionally installed in `src/index.js` when `__SERVER__` is true
</important>
107 changes: 107 additions & 0 deletions .rpiv/guidance/src/components/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Components Layer

## Responsibility
Custom UI components for Climate-ADAPT: content editor blocks (`manage/`), public-facing views and widgets (`theme/`), and search result item renderers (`Result/`). Sits atop Volto core and EEA design system addons.

## Dependencies
- **@plone/volto**: Block config API, helpers, Redux actions
- **semantic-ui-react**: Layout primitives (Grid, Label, etc.)
- **@eeacms/volto-openlayers-map**: OpenLayers wrapper for map blocks
- **react-intl**: Internationalization (`defineMessages`, `FormattedMessage`, `useIntl`)
- **@eeacms/search**: `useAppConfig`, `registry.resolve` for search result components

## Consumers
- **`src/index.js`**: Registers components as views, widgets, and app extras
- **`src/components/manage/Blocks/index.js`**: Composes all block installers into `config.blocks.blocksConfig`
- **Volto core**: Consumes blocks via the standard block rendering system

## Module Structure
```
src/components/
├── index.js # Barrel exports
├── manage/ # Content editor components
│ ├── Blocks/ # One directory per block type
│ │ ├── {BlockName}/ # index.js (installer) + Edit.js + View.js (+ helpers)
│ │ ├── withResponsiveContainer.jsx # Shared HOC — responsive container sizing
│ │ └── withVisibilitySensor.jsx # Shared HOC — lazy render on scroll into view
│ ├── CreateArchivedCopyButton/ # Toolbar button for archiving
│ └── Workflow/ # Link integrity modal for workflow transitions
├── theme/ # Public-facing components
│ ├── Views/ # Content type views (one per @type)
│ ├── Widgets/ # Form widgets (geochars, geolocation, promotional_image)
│ ├── ASTNavigation/ # Adaptation Support Tool navigation
│ ├── MissionSignatoryProfile/ # Signatory profile with tabbed sections
│ └── {Component}/ # Utility components (AccordionList, BannerTitle, etc.)
├── Result/ # Custom search result item renderers
└── manage/TransparentOverlay.jsx # Full-screen overlay for async operations
```

## Block Installer Pattern (CRITICAL: `config` composition)

Each block is an installer function that adds an entry to `config.blocks.blocksConfig` and returns `config`. All installers are composed in `Blocks/index.js` via Redux `compose()`.

```javascript
// {BlockName}/index.js
export default function installBlock(config) {
config.blocks.blocksConfig.myBlock = {
id: 'myBlock',
title: 'My Block',
icon: iconSVG,
group: 'site',
edit: Edit,
view: View,
security: { addPermission: [], view: [] },
};
return config;
}

// Blocks/index.js — Compose all installers
export default function installBlocks(config) {
return compose(installRAST, installReadMore, /* ... */)(config);
}
```

## Edit Wraps View Pattern (Default)

Most blocks share a single View and pass `mode="edit"` from Edit. Block settings are edited via the **Block sidebar**, not inline. Use separate Edit+View only when the block needs a custom in-canvas editor.

```javascript
// Edit.js
import View from './View';
export default function MyBlockEdit(props) {
return <View {...props} mode="edit" />;
}

// View.js — Branches on mode
export default function MyBlockView({ mode, block, data }) {
const isEditing = mode === 'edit';
}
```

## Block Styles Convention

Each block gets its own `styles.less` co-located in the block directory — never in global theme files.

## Architectural Boundaries
- **NO direct Redux dispatch in theme components**: Use Volto's `@plone/volto/actions` or custom actions from `src/store/actions/`
- **NO server-side rendering of map blocks**: Guard with `if (__SERVER__) return <Loading />`
- **NO inline styles for layout**: Use LESS files or Semantic UI classes

<important if="you are adding a new block to this layer">
## Adding a New Block
1. Create `src/components/manage/Blocks/{BlockName}/` directory
2. Add `index.js` — installer function registering in `config.blocks.blocksConfig`
3. Add `View.js` — the main rendering component (handles both view and edit modes)
4. Add `Edit.js` — wraps View with `mode="edit"` (or create separate Edit if inline editing needed)
5. Add `styles.less` if custom styling is needed (imported from View.js)
6. Register the installer in `src/components/manage/Blocks/index.js` compose chain
7. Set `restricted` if the block should only appear in certain contexts (use `blockAvailableInMission`)
</important>

<important if="you are writing or modifying tests for this layer">
## Testing Conventions
- Jest test files use `.test.jsx` suffix, co-located with components
- Test snapshots in `__snapshots__/` subdirectories
- Run via `make test` (Docker) or `CI=true make test` for CI mode
- Mock external dependencies (e.g., `GeolocationWidgetMapContainer` in `__mocks__/`)
</important>
70 changes: 70 additions & 0 deletions .rpiv/guidance/src/customizations/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Customizations Layer (Component Shadowing)

## Responsibility
Overrides of Volto core, EEA addon, and plone-collective components via Volto's shadowing system. Each shadowed component must have a `README.md` explaining the rationale. Mirrors the original package path under `src/customizations/`.

## Dependencies
- **@plone/volto**: Core components, reducers, middleware being shadowed
- **@eeacms/* addons**: EEA design system and theme components being overridden
- **Redux**: For reducer and middleware shadowing

## Consumers
- **Volto build system**: Automatically resolves shadowed components before core versions
- **`src/components/manage/Workflow/`**: The Workflow shadow imports `WorkflowLinkIntegrityModal` from the components layer

## Module Structure
```
src/customizations/
├── volto/ # Shadow core Volto components
│ ├── components/manage/ # Editor-side overrides
│ │ ├── Workflow/ # Link integrity check on private transitions
│ │ ├── Contents/ # Physical breadcrumbs instead of standard
│ │ ├── Multilingual/ # Translation object customization
│ │ ├── Widgets/ # ObjectBrowserWidget override
│ │ ├── View/ # DefaultView, LinkView, View overrides
│ │ └── ...
│ ├── components/theme/ # Public-facing overrides (App, Header, Sitemap)
│ ├── helpers/ # Helper function overrides (LanguageMap, Url)
│ ├── reducers/ # Redux reducer overrides (actions, breadcrumbs, navigation)
│ └── middleware/ # API middleware override
├── @eeacms/ # Shadow EEA addon components
│ ├── volto-eea-design-system/ui/Header/ # Header menu and search popups
│ ├── volto-block-style/ # StyleWrapper schema override
│ ├── volto-listing-block/ # Listing block cards and item templates
│ └── volto-banner/ # Banner override
├── @plone/volto-slate/ # Slate editor overrides (Table, Text, Image, normalize)
└── @plone-collective/ # Community addon overrides
├── volto-authomatic/ # Login component
└── volto-rss-provider/ # Express middleware
```

## Shadowing Pattern

Mirror the original file path from the target package. Every shadowed component MUST include a `README.md` (or `Readme.md`) explaining what was changed and why.

```
# Original path:
@plone/volto/components/manage/Workflow/Workflow.jsx

# Shadow path:
src/customizations/volto/components/manage/Workflow/Workflow.jsx
+ README.md
```

## Volto Slate Override Pattern

The `@plone/volto-slate` shadowing is extensive — covering Table deconstruction, Text block, Image plugin, normalization, and block utilities. These are needed because Climate-ADAPT uses a patched Volto-slate fork with custom behavior for table handling and image processing.

## Architectural Boundaries
- **NO shadowing without README.md**: Every shadowed file must document its purpose
- **NO silent passthrough**: If a shadowed component only delegates to the original, remove the shadow — it adds maintenance burden
- **Minimize shadow surface**: Prefer Volto's config API (`config.blocks`, `config.views`, etc.) over shadowing when possible

<important if="you are shadowing a new core component">
## Shadowing a New Component
1. Determine the original file path in the target package (e.g., `@plone/volto/components/manage/X/X.jsx`)
2. Create `src/customizations/volto/components/manage/X/X.jsx` mirroring the path
3. Add `README.md` in the same directory explaining the modification
4. Import from `@plone/volto/components/manage/X/X.jsx` if you need the original as a base
5. For EEA addons, use `src/customizations/@eeacms/{addon-name}/` prefix
</important>
93 changes: 93 additions & 0 deletions .rpiv/guidance/src/search/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Search Configuration Layer

## Responsibility
Configures multiple Elasticsearch-powered search engines for Climate-ADAPT's different portal areas: main search, health observatory, and EU mission sub-portals (stories, tools, projects, funding, all). Built on `@eeacms/volto-searchlib` (Search UI wrapper).

## Dependencies
- **@eeacms/search** (`volto-searchlib`): `mergeConfig`, search UI framework, result components
- **@elastic/search-ui**: Underlying Elasticsearch client and React bindings
- **`src/components/Result/`**: Custom result item renderers registered via `config.settings.searchlib.resolve`

## Consumers
- **`src/index.js`**: Calls `installSearchEngine(config)` to apply all search configs
- **Search page components**: Consume `config.searchui` entries to render search interfaces

## Module Structure
```
src/search/
├── index.js # Installer — chains all engine configs, applies shared extraQueryParams
├── utils.js # Shared utilities (date formatting, proxy address, thumb URL)
├── vocabulary.js # Shared search vocabulary definitions
└── {engine-name}/ # One directory per search engine
├── config.js # Main config — merges with globalsearchbase, sets index/host/facets
├── config-*.js # Variant configs (e.g., config-health.js)
├── facets.js # Facet definitions (fields, filters, display options)
└── views.js # Result view configurations (tiles, cards, landing pages)
```

## Search Engine Config Pattern

Each engine config function receives `config`, merges with the global search base, and sets engine-specific parameters. All engines share `extraQueryParams` (date decay scoring, text field boosting) applied in `index.js`.

```javascript
// cca/config.js — Main search engine
import { mergeConfig } from '@eeacms/search';
import facets from './facets';
import views from './views';
import { vocab } from '../vocabulary';

export default function installMainSearch(config) {
config.searchui.ccaSearch = {
...mergeConfig(envConfig, config.searchui.globalsearchbase),
elastic_index: '_es/globalsearch',
index_name: 'data_searchui',
host: process.env.RAZZLE_ES_PROXY_ADDR || 'http://localhost:3000',
vocab,
};
config.searchui.ccaSearch.facets = facets;
// ... content sections, permanent filters, etc.
return config;
}

// index.js — Chain all engines
const applyConfig = (config) => {
config.settings.searchlib = [
installMainSearch,
installHealthSearch,
installMissionStoriesSearch,
// ... more engines
].reduce((acc, cur) => cur(acc), config.settings.searchlib);

// Apply shared query params to all engines
const searchui = config.settings.searchlib.searchui;
searchui.ccaSearch.extraQueryParams = extraQueryParams;
searchui.ccaHealthSearch.extraQueryParams = extraQueryParams;
// ...
return config;
};
```

## Custom Result Views

Custom result item components (e.g., `HealthHorizontalCardItem`, `ClusterHorizontalCardItem`) are registered in `src/index.js` via `config.settings.searchlib.resolve` and referenced by name in the search engine's view config.

```javascript
// src/index.js
config.settings.searchlib.resolve.HealthHorizontalCardItem = {
component: HealthHorizontalCardItem,
};
```

## Architectural Boundaries
- **NO hardcoded Elasticsearch URLs in config**: Use `RAZZLE_ES_PROXY_ADDR` env var or `getClientProxyAddress()` for client-side fallback
- **NO engine-specific styles**: Search styling comes from `@eeacms/search` and global `theme/globals/search.less`

<important if="you are adding a new search engine">
## Adding a New Search Engine
1. Create `src/search/{engine-name}/` directory
2. Add `config.js` — merge with `globalsearchbase`, set `elastic_index`, `host`, `vocab`
3. Add `facets.js` — define facet fields, filter types, display options
4. Add `views.js` — define result views, tiles, landing page params
5. Register in `src/search/index.js` — add to the reduce chain and apply `extraQueryParams`
6. If custom result items needed, create in `src/components/Result/` and register in `src/index.js` via `searchlib.resolve`
</important>
Loading