Introduction
This is the monorepo for the DSP Repository.
The DaSCH Service Platform (DSP) consists of two main components:
- DSP VRE: The DSP Virtual Research Environment (VRE),
where resdearchers can work on their data during the lifetime of the project.
It consists of the DSP-APP, DSP-API and DSP-TOOLS.
The DSP VRE is developed in various other git repositories. - DSP Repository: The DSP Repository is the long-term archive for research data.
It consists of the DSP Archive and the Discovery and Presentation Environment (DPE).
The DSP Repository is developed in this monorepo.
Additionally, the monorepo contains the DSP Design System and DaSCH's website.
This documentation provides an overview of the project structure. It covers the different components of the system architecture, the design system we use for the development, and the processes we follow for working on the DSP Repository, including onboarding information, etc.
About this Documentation
This documentation is built using mdBook.
Pre-requisites
Before contributing, please ensure you have the following installed:
Any further dependencies can be installed using just
commands:
just install-requirements
Building and Serving the Documentation
To run the documentation locally, use:
just docs-serve
Contributing to the Documentation
mdBook uses Markdown for documentation.
The documentation is organized into chapters and sections, which are defined in the SUMMARY.md
file.
Each section corresponds to a Markdown file in the src
directory.
To configure the documentation (e.g. adding plugins), modify the book.toml
file.
Deployment
This documentation is not yet deployed. The deployment process will be defined in the future.
Workflows and Conventions
Entry Points
The first entry point of this repository is the README file, which should give anyone an indication of where to find any information they need.
For any interaction or coding-related workflow, the justfile is the primary source of truth. The justfile contains all the commands and workflows that are used in this repository, along with their descriptions.
Key Development Commands
The justfile provides self-documenting commands. Key workflows include:
just check
- Run formatting and linting checksjust build
- Build all targetsjust test
- Run all testsjust run
- Run main serverjust watch
- Watch for changes and run testsjust run-watch-playground
- Run design system playground with hot reloadjust playground install
- Install playground dependenciesjust playground test
- Run design system tests
Run just
without arguments to see all available commands with descriptions.
Any further information should be located in the documentation.
Git Workflow
For this repository, we use a rebase workflow. This means that all changes should be made on a branch, and then rebased onto the main branch before being merged.
This allows us to keep a clean commit history and avoid merge commits.
Project Structure and Code Organization
Overview
This repository contains the source code for the DSP Repository. It is structured as a Rust workspace, with multiple crates.
All Rust crates are organized as subdirectories within the modules/
directory.
Additionally, any non-Rust code or assets are placed either in the assets/
directory
or as a separate module within the modules/
directory.
Project Structure
[!WARNING]
This page is not up-to-date.
Workspace Layout
.
├── Cargo.toml # Workspace manifest
├── types/ # Domain models, traits, and shared errors
├── services/ # Pure business logic implementations
├── storage/ # Data persistence (DB, in-memory) implementations
├── html_api/ # HTML routes, templates, and SSE endpoints
├── json_api/ # JSON RPC/REST endpoints
├── server/ # Web server binary crate
└── cli/ # CLI binary crate for tools and scripts
Crate Responsibilities
types/
– Domain Types and Interfaces
This crate defines all core data structures, error types, and trait interfaces shared across the application. It is dependency-light and logic-free.
- Domain models (
ProjectCluster
,ResearchProject
,Collection
,Dataset
,Record
, etc.) - Error types (
AppError
) - Trait definitions:
- Service traits:
MetadataService
- Repository traits:
MetadataRepository
- Service traits:
Example:
#![allow(unused)] fn main() { pub trait MetadataRepository { async fn find_by_id(&self, id: &str) -> Result<ResearchProject, AppError>; } }
services/
– Business Logic
Implements the types::service
traits and contains all pure application logic.
- Depends on
types
- Free of side effects and I/O
- Easily testable
- Orchestrates workflows and enforces business rules
Example:
#![allow(unused)] fn main() { pub struct MetadataServiceImpl<R: MetadataRepository> { pub repo: R, } #[async_trait] impl<R: MetadataRepository> MetadataService for MetadataServiceImpl<R> { async fn find_by_id(&self, id: &str) -> Result<ResearchProject, AppError> { self.repo.find_by_id(id).await } } }
storage/
– Persistence Layer
Implements the types::storage
traits to access data in external systems such as SQLite or in-memory stores.
- No business logic
- Easily swappable with mocks or test implementations
Example:
#![allow(unused)] fn main() { pub struct InMemoryMetadataRepository { ... } #[async_trait] impl MetadataRepository for InMemoryMetadataRepository { async fn find_by_id(&self, id: &str) -> Result<ResearchProject, AppError> { // In-memory lookup logic } } }
html_api/
– HTML Hypermedia + SSE
Handles the user-facing UI layer, serving HTML pages and fragments, and live-updating data via SSE.
- Maud templates
- Datastar for link generation
- SSE endpoints for live features like notifications, progress updates
- Routes for page rendering and form submissions
Example:
#![allow(unused)] fn main() { #[get("/users/:id")] async fn user_profile(State(app): State<AppState>, Path(id): Path<Uuid>) -> impl IntoResponse { let user = app.user_service.get_user_profile(id).await?; HtmlTemplate(UserProfileTemplate { user }) } }
http_api/
– Machine-Readable HTTP API
Exposes your application logic through a structured JSON API for integration with JavaScript frontends or third-party services.
- Cleanly separates business logic from representation
- Handles serialization and input validation
Example:
#![allow(unused)] fn main() { #[get("/api/users/:id")] async fn get_user(State(app): State<AppState>, Path(id): Path<Uuid>) -> impl IntoResponse { let user = app.user_service.get_user_profile(id).await?; Json(user) } }
server/
– Web Server Binary
This crate is the entrypoint for running the full web application.
- Loads configuration
- Initializes services and storage
- Combines all route layers (
html_api
,http_api
) - Starts the Axum server
Example:
#[tokio::main] async fn main() -> Result<(), AppError> { let storage = PostgresUserRepository::new(...); let service = UserServiceImpl { repo: storage }; let app = Router::new() .merge(html_api::routes(service.clone())) .merge(http_api::routes(service.clone())); axum::Server::bind(&addr).serve(app.into_make_service()).await?; Ok(()) }
cli/
– Command-Line Interface
Provides a CLI for administrative or batch tasks such as:
- Import/export of data
- Cleanup scripts
- Background migrations
- Developer utilities
Example (using clap):
#![allow(unused)] fn main() { #[derive(Parser)] enum Command { ImportUsers { file: PathBuf }, ReindexSearch, } }
Run via:
cargo run --bin cli -- import-users ./users.csv
Benefits of This Structure
Aspect | Benefit |
---|---|
Separation of concerns | Clear boundaries between domain, logic, persistence, and delivery |
Modular | Each crate can be tested and reused independently |
Team-friendly | Frontend-focused devs work in html_api ; backend devs focus on services and storage |
Testable | Services and repositories can be mocked for unit/integration testing |
Extensible | Add more APIs (e.g., GraphQL, CLI commands) without modifying existing code |
Development Guidelines
- Never put business logic in route handlers. Use the service layer.
- Keep domain models and interfaces free of framework dependencies.
- Each crate has a single responsibility.
- SSE endpoints live in
html_api
, not as a separate API crate. - Prefer async traits for I/O-related operations.
- Write integration tests in the same crate or create a top-level
tests/
crate for system-wide tests.
Future Growth Possibilities
- Add a
worker/
crate for background jobs - Add a
scheduler/
crate for periodic tasks - Add a
tests/
crate for orchestrated integration tests - Add a
graphql_api/
oradmin_api/
if needed
Getting Started
To run the application server:
cargo run --bin server
To run the CLI:
cargo run --bin cli -- help
Summary
This modular design ensures clarity, maintainability, and smooth collaboration for both backend and frontend developers. The split between crates follows clean architecture principles and allows for focused development, rapid iteration, and clear testing strategies.
DSP Design System
Introduction
The DSP Design System is a customization of the IBM Carbon Design System. It follows Carbon in terms of design language and implementation. It is customized in the following ways:
- It is not a general purpose design system, but a purpose-built system. As such, it is much smaller and less complex.
- It is not generic or customizeable, instead the DSP brand is baked into it, thus simplifying complexity if use.
- It is purposefully kept small:
- It comes with two themes (dark and light, corresponding to "gray-90" and "gray-10" in Carbon) and no option for custom theming.
- The set of available styles (colors, typography, spacing, etc.) is kept intentionally small to promote consistent user interfaces.
- It only has the components that are strictly needed. Additional components may be added, when necessary.
- It only has the component variants that are strictly needed. Additional component variants may be added, when necessary.
- It may have purpose-specific components. (E.g. Carbon does not provide a "Card" component, but rather a "Tile" component, from which cards can be built. The DSP Design System instead would provide a "Card" component.)
Current Implementation Status
The DSP Design System is currently in early development with the following components implemented:
Available Components
Button
- Implementation: Native Maud
- Variants: Primary, Secondary, Outline
- Status: 🚧 Functional but incomplete (styling verification needed, missing accessibility features)
- Features: Basic button functionality with variant support, disabled state, custom test IDs
Banner
- Implementation: Native Maud
- Variants: Accent only, with prefix, with suffix, full (prefix + accent + suffix)
- Status: ✅ Fully functional
- Features: Semantic HTML with proper structure, configurable text sections with accent styling
Shell
- Implementation: Carbon Web Components (CDN-based)
- Purpose: Application navigation and layout wrapper
- Status: ✅ Fully functional with advanced features
- Features: Responsive navigation header, search functionality, theme toggle with persistence, side navigation, accessible ARIA labels
Tile
- Implementation: Native Maud
- Variants: Base, Clickable
- Status: 🚧 Functional but incomplete (styling verification needed, missing accessibility features)
- Features: Content containers accepting arbitrary Markup, custom test IDs
Link
- Implementation: Carbon Web Components (CDN-based)
- Purpose: Navigation and external links
- Status: ✅ Functional (temporary implementation)
- Features: Carbon-styled links with brand colors and hover states
Tag
- Implementation: Carbon Web Components (CDN-based)
- Variants: Gray, Blue, Green
- Status: 🚧 Functional but incomplete (missing variants and attributes)
- Features: Colored labels with brand-aligned styling, dynamic script loading
Development Environment
Playground
- URL: http://localhost:3400 (via
just run-watch-playground
) - Architecture: Full-featured development environment with Rust server, TypeScript testing, and visual regression
- Features:
- Shell Interface: Sidebar navigation with component list and active states
- Component Isolation: Iframe-based component rendering with parameter controls
- Variant Selection: Dynamic component variant switching with real-time updates
- Theme Switching: Light/dark theme toggle with live preview
- Documentation Tabs: Component and documentation view switching
- Live Reload: WebSocket-based automatic refresh on file changes
- Testing: Comprehensive TypeScript/Playwright test suite with functional, accessibility, responsive, and visual regression tests
- Commands:
just playground install
- Install frontend dependencies and browsersjust playground test
- Run all automated testsjust playground test-visual
- Run visual regression testsjust playground test-headed
- Run tests with browser visible
Component Architecture Decision
Composability Approach
We use a Maud-Native + Props combination for component composability:
Core Principles
- Maud
Markup
Return Types: Components returnmaud::Markup
instead ofString
for zero-copy composition - Props Structs: Complex components use dedicated props structs for clear parameter grouping
- Simple Functions: Basic components remain as simple functions
- Flexible Text Input: Use
impl Into<String>
for text parameters
Component Patterns
Simple Components
#![allow(unused)] fn main() { use maud::{Markup, html}; pub fn button(text: impl Into<String>) -> Markup { html! { button .dsp-button { (text.into()) } } } }
Complex Components with Props
#![allow(unused)] fn main() { pub struct CardProps { pub title: String, pub content: Markup, pub variant: CardVariant, } pub fn card(props: CardProps) -> Markup { html! { div .dsp-card { h2 .dsp-card__title { (props.title) } div .dsp-card__content { (props.content) } } } } }
Component Composition
#![allow(unused)] fn main() { // Direct nesting - zero-copy composition pub fn page_header(title: impl Into<String>, actions: Markup) -> Markup { html! { header .dsp-page-header { h1 { (title.into()) } div .dsp-page-header__actions { (actions) // Direct Markup insertion } } } } }
Benefits
- Efficient: No string concatenation overhead
- Type Safe: Compile-time guarantees for component structure
- Composable: Components nest naturally without conversion
- Extensible: Props structs make adding parameters easy
- Consistent: Unified approach across all components
Migration Path
- Convert existing components to return
Markup
- Add props structs for components with 3+ parameters
- Use
impl Into<String>
for text inputs - Test composition in playground
Temporary Carbon Web Components Usage
Note: Some components currently use Carbon Design System web components via CDN with wrapper functions as a temporary solution. This approach uses PreEscaped
HTML with conditional script loading for rapid development. These will be replaced with native Maud implementations following the architecture above as the design system matures.
Button
Interactive button component for user actions.
Usage Guidelines
Use buttons to trigger actions, submit forms, or navigate to different sections of the application. Choose the appropriate variant based on the action's importance and context.
Variants
Primary
The primary button is used for the most important action on a page. Use sparingly - typically only one primary button per page or section.
Secondary
Secondary buttons are used for less important actions that still need emphasis. They can be used multiple times on a page.
Outline
Outline buttons are used for tertiary actions or when you need a lighter visual treatment while maintaining accessibility.
Accessibility Notes
Section missing...
Implementation Status
🚧 Functional but incomplete - styling verification needed, missing accessibility features
Design Notes
The button component follows Carbon Design System patterns with DSP brand customizations. Spacing and typography align with the design system tokens.
Button vs. Link
Use buttons for actions that trigger changes or submit data. Use links for navigation to other pages or sections. Buttons should not be used for navigation purposes.
Banner
Banner component with multiple display variants for highlighting important information.
Usage Guidelines
The banner component should only be used on landing pages etc. as a prominent way to display a core message. Use the appropriate variant based on the content structure and emphasis needed.
Variants
- Accent Only
- With Prefix
- With Suffix
- Full
The banner is structured as follows: The first line is an optional prefix, the second line - which is of larger font size - is the main message, and the third line is an optional suffix.
Accessibility Notes
- Semantic HTML structure ensures proper screen reader navigation
- High contrast colors meet WCAG AA standards
- Text is properly structured for assistive technologies
Implementation Status
✅ Fully functional - complete implementation with all variants
Design Notes
None
Link
Link component for navigation and external links.
Usage Guidelines
Use links for navigation between pages, sections, or external resources. The link component provides consistent styling across the application.
Variants
Default
Standard link styling with brand colors and hover states. Suitable for all link use cases.
Accessibility Notes
- Proper semantic HTML anchor tags
- Clear focus indicators for keyboard navigation
- Sufficient color contrast for readability
- External links include appropriate indicators
Implementation Status
✅ Functional - currently using Carbon Web Components as temporary implementation
Design Notes
Currently implemented using Carbon Web Components via CDN. This provides Carbon-styled links with brand colors and hover states. Will be migrated to native Maud implementation following the component architecture patterns.
Link vs. Button
Use links for navigation purposes. Use buttons for actions that trigger changes or submit data. Links should not be used for actions that modify state or submit forms.
Shell
Application shell component providing navigation and layout wrapper with configurable navigation elements.
Usage Guidelines
Use the shell component as the main application wrapper. It provides consistent navigation, theme management, and layout structure across the application.
API
shell(header_nav_elements: Vec<NavElement>)
Create a shell with header navigation using the builder pattern.
#![allow(unused)] fn main() { use components::shell::{shell, NavElement, NavItem, NavMenu, NavMenuItem}; // Shell with header navigation only let header_nav = vec![ NavElement::Item(NavItem { label: "Home", href: "/" }), NavElement::Item(NavItem { label: "Projects", href: "/projects" }), NavElement::Menu(NavMenu { label: "Resources", items: vec![ NavMenuItem { label: "Documentation", href: "/docs" }, NavMenuItem { label: "Tutorials", href: "/tutorials" }, ], }), ]; let shell_markup = shell(header_nav).build(); }
with_side_nav(side_nav_elements: Vec<NavElement>)
Optionally add side navigation to the shell.
#![allow(unused)] fn main() { let side_nav = vec![ NavElement::Item(NavItem { label: "Dashboard", href: "/dashboard" }), NavElement::Menu(NavMenu { label: "My Work", items: vec![ NavMenuItem { label: "Active Projects", href: "/work/active" }, NavMenuItem { label: "Drafts", href: "/work/drafts" }, ], }), ]; let shell_markup = shell(header_nav) .with_side_nav(side_nav) .build(); }
with_content(content: Markup)
Add content to the shell's main content area.
#![allow(unused)] fn main() { use maud::html; let content = html! { div style="padding: 2rem;" { h1 { "Welcome to the Application" } p { "This is the main content area of the shell." } } }; let shell_markup = shell(header_nav) .with_content(content) .build(); }
Complete Example
Full shell configuration with navigation and content:
#![allow(unused)] fn main() { use components::shell::{shell, NavElement, NavItem, NavMenu, NavMenuItem}; use maud::html; let header_nav = vec![ NavElement::Item(NavItem { label: "Home", href: "/" }), NavElement::Item(NavItem { label: "Projects", href: "/projects" }), NavElement::Menu(NavMenu { label: "Resources", items: vec![ NavMenuItem { label: "Documentation", href: "/docs" }, NavMenuItem { label: "API Reference", href: "/api" }, ], }), ]; let side_nav = vec![ NavElement::Item(NavItem { label: "Dashboard", href: "/dashboard" }), NavElement::Menu(NavMenu { label: "My Work", items: vec![ NavMenuItem { label: "Active Projects", href: "/work/active" }, NavMenuItem { label: "Drafts", href: "/work/drafts" }, ], }), ]; let content = html! { div style="padding: 2rem; max-width: 1200px; margin: 0 auto;" { h1 { "Application Dashboard" } p { "Welcome to your personalized dashboard." } div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1rem; margin-top: 2rem;" { div style="padding: 1rem; background: var(--cds-background-hover); border-radius: 4px;" { h3 { "Recent Activity" } p { "Your latest updates and changes." } } div style="padding: 1rem; background: var(--cds-background-hover); border-radius: 4px;" { h3 { "Quick Actions" } p { "Frequently used tools and shortcuts." } } } } }; let shell_markup = shell(header_nav) .with_side_nav(side_nav) .with_content(content) .build(); }
Builder Pattern
The shell uses a builder pattern for clean and flexible configuration:
- Required: Header navigation elements must be provided
- Optional: Side navigation can be added with
with_side_nav()
- Optional: Content can be added with
with_content()
- Fluent API: Method chaining for readable configuration
- Type Safety: Cannot create shell without header navigation
Navigation Structure
The shell supports flexible navigation with two types of elements:
Navigation Items
Single navigation links that appear in both header and side navigation.
#![allow(unused)] fn main() { NavElement::Item(NavItem { label: "Home", href: "/" }) }
Navigation Menus
Dropdown menus with multiple items that appear as dropdowns in header navigation and expandable menus in side navigation.
#![allow(unused)] fn main() { NavElement::Menu(NavMenu { label: "Resources", items: vec![ NavMenuItem { label: "Documentation", href: "/docs" }, NavMenuItem { label: "API Reference", href: "/api" }, ], }) }
Features
- Configurable Navigation: Support for both navigation items and dropdown menus
- Responsive Design: Adapts to different screen sizes with collapsible side navigation
- Search Functionality: Integrated search with accessibility features
- Theme Toggle: Light/dark theme switching with persistence
- Static Branding: DaSCH Service Platform branding remains consistent
- Accessible ARIA Labels: Full accessibility support
Accessibility Notes
- Comprehensive ARIA labels and roles
- Keyboard navigation support
- Screen reader optimized structure
- Focus management for modal states
- High contrast theme support
- Semantic navigation structure
Implementation Status
✅ Fully functional with configurable navigation
Design Notes
Currently implemented using Carbon Web Components via CDN. Provides advanced features including responsive navigation, search functionality, and theme management. The shell serves as the primary application wrapper and maintains consistent layout across different pages.
Navigation content is now configurable while maintaining static branding elements and core functionality.
Tag
Tag component with different color variants for categorization and labeling.
Usage Guidelines
Use tags to categorize content, show status, or provide quick visual identification. Choose colors that align with your content's meaning and maintain consistency across the application.
Variants
Gray
Default neutral tag for general categorization and labeling.
Blue
Blue tag for information, links, or primary category identification.
Green
Green tag for success states, completed items, or positive categorization.
Accessibility Notes
- Semantic HTML structure
- Sufficient color contrast for all variants
- Screen reader compatible text
- Clear visual differentiation between variants
Implementation Status
🚧 Functional but incomplete - missing variants and attributes
Design Notes
Currently implemented using Carbon Web Components via CDN. Provides colored labels with brand-aligned styling and dynamic script loading. Will be extended to include additional variants and attributes as needed.
Tile
Tile component with base and clickable variants for content containers.
Usage Guidelines
Use tiles to group related content in a visually distinct container. Choose the clickable variant when the entire tile should be interactive.
Variants
Base
Static content container for displaying information without interaction.
Clickable
Interactive tile that responds to user interaction. Use when the entire tile should be clickable.
Accessibility Notes
- Proper semantic HTML structure
- Keyboard navigation support for clickable variant
- Clear focus indicators
- Screen reader compatible content structure
Implementation Status
🚧 Functional but incomplete - styling verification needed, missing accessibility features
Design Notes
The tile component serves as a content container accepting arbitrary Markup. Custom test IDs are supported for testing. The clickable variant includes proper interaction states and accessibility features.
Web Components Overview
DaSCH publishes a small number of purpose-built web components. These components are intended for external use, allowing developers to integrate them into their own applications.
We do not publish our design system components as web components.
Web components are organized by functionality in the web_components/
directory.
Web Components
attribution
:
A badge component that external data providers can use to attribute their data if it is hosted on DaSCH.
Attribution Badge
The Attribution Badge is a web component that marks data as being archived at DaSCH.
We kindly ask data providers or project specific websites to use this badge to attribute their data,
if large amounts of data are served from DaSCH.
We do not expect this badge to be used for small amounts of data, such as single resources.
Badges
The badge comes in two variants, a small "tag-like" variant and a larger "card-like" variant. The card-like variant has a light and a dark theme.
Usage
Currently, the web commponents are not published to a package registry. When published, they can be used by importing the them as a JavaScript module.
Basic Usage
To display the badge, you can use the following HTML:
For the small variant:
<dsp-attribution-badge></dsp-attribution-badge>
For the large variant:
<dasch-data-attribution-card></dasch-data-attribution-card>
Data Attributes
permalink
Both badge and card components support a permalink
attribute that allows customizing the URL they link to.
When no permalink is provided, components default to linking to https://www.dasch.swiss/
.
This attribute is useful for linking directly to specific datasets or projects archived at DaSCH.
<dasch-data-attribution-badge permalink="https://ark.dasch.swiss/ark:/72163/1/0000/xyz"></dasch-data-attribution-badge>
<dasch-data-attribution-card permalink="https://ark.dasch.swiss/ark:/72163/1/0000/xyz"></dasch-data-attribution-card>
theme
The card component supports a theme
attribute with two values: "light"
(default) and "dark"
.
This allows the component to adapt to different design contexts. The badge component does not support theming.
<dasch-data-attribution-card theme="light"></dasch-data-attribution-card>
<dasch-data-attribution-card theme="dark"></dasch-data-attribution-card>
While any link may work, we strongly recommend using a permalink (ARK URL) as provided by DaSCH. This ensures that the link remains stable and persistent over time.
Style Customization
The components expose CSS custom properties (CSS variables) that can be customized to inject colors into the components.
We recommend not to make use of this, unless you have a specific need to adapt the colors, e.g. for accessibility reasons.
Badge Component
dasch-data-attribution-badge {
--dasch-attribution-primary: hsl(206 48% 38%); /* Badge background color */
--dasch-attribution-text: hsl(0, 0.00%, 90.30%); /* Text color */
}
Card Component
dasch-data-attribution-card {
--dasch-attribution-primary: hsl(206 48% 38%); /* Primary accent color */
--dasch-attribution-secondary: hsl(210 49% 63%); /* Secondary accent color */
--dasch-attribution-bg-light: hsl(0, 0.00%, 96.30%); /* Light theme background */
--dasch-attribution-bg-dark: hsl(0, 0.00%, 9.20%); /* Dark theme background */
--dasch-attribution-text-light: hsl(0, 0.00%, 90.30%); /* Light theme text */
--dasch-attribution-text-dark: hsl(0, 0.00%, 15.20%); /* Dark theme text */
}
Demo
An interactive demo is available at modules/webcomponents/attribution/demo.html
.
The demo showcases both components with various configuration options
including different themes, custom permalinks, and styling examples.
Discovery, Design and Development Process
Any work on the DSP Repository is done in collaboration between Management, Product Management (PM), the Research Data Unit (RDU) and Development (Dev). The process should look as outlined below, but may be adjusted to fit the needs of the project and the team.
In discovery, PM validates that an opportunity is aligned with DaSCH's strategy. In collaboration with the RDU, PM verifies that the opportunity can provide a desirable outcome.
PM will create a project description, including low-fidelity wireframes (Balsamiq or pen and paper), based on which they define user flows and journeys.
If any design components are needed, these will be added to the design system.
Finally, high-fidelity wireframes will be created in Figma, if needed.
Based on the project description and the wireframes, Dev will refine the project description, create Linear tickets and implement it accordingly.
When the implementation is done, PM will verify that the outcome was achieved and identify opportunities for further improvements.
Tech Stack
Core Technologies
- Rust - Primary development language (Edition 2021, Toolchain 1.86.0)
- Axum - HTTP web framework with WebSocket support
- Maud - Macro-based templating engine (JSX-like syntax)
- DataStar - Hypermedia-driven frontend interactivity
- Carbon Web Components - Design system components via CDN integration
- Database TBD - Currently using static JSON files
Development & Testing
- TypeScript + Playwright - End-to-end and visual regression testing for design system
- Cargo nextest - Parallel test execution
- ESLint + Prettier - JavaScript/TypeScript code quality (testing only)
- Node.js ecosystem - Supporting testing infrastructure
Architecture Principles
We keep the design evolutionary, starting from the simplest possible solution and iterating on it. At first, providing data from static JSON files, or working with static content, is sufficient. Following clean architecture principles, swapping out the persistence layer is easy.
Implementation Notes
The TypeScript ecosystem is used exclusively for testing and development tooling, not for production runtime code. The core application remains purely Rust-based with hypermedia-driven interactivity.
Testing and Quality Assurance
We follow the Testing Pyramid approach to testing, the majority of tests are unit tests, with a smaller number of integration tests, and a few end-to-end tests.
Unit and integration tests are written in Rust, end-to-end tests are written either in Rust or in JavaScript using Playwright.
Design System Testing
The design system playground includes comprehensive testing infrastructure:
Interactive Testing (MCP):
- Start playground server:
just run-watch-playground
- Use Claude Code with Playwright MCP commands for visual verification
- Commands whitelisted in
.claude/settings.json
- Best for: Component development, design verification, manual testing
Automated Testing (CI/CD):
- TypeScript-based Playwright setup with tooling
- Functional, accessibility, and responsive design testing in CI
- Visual regression testing (local development only)
- ESLint, Prettier, and TypeScript checking
- HTML + JSON reporters for CI/CD integration
- Best for: End-to-end user flows, automated regression detection
Setup: just playground install
then just playground test
Key Commands:
just playground test
- Run all testsjust playground test-visual
- Visual regression testsjust playground test-ui
- Interactive test runnerjust playground test-debug
- Debug mode with browser DevToolsjust playground update-visuals
- Update visual baselines (platform-specific)just playground type-check
- TypeScript validationjust playground lint-and-format
- Code quality checks
Visual Testing: Visual regression tests run locally only to avoid cross-platform font rendering differences. CI runs functional, accessibility, and responsive tests.
For single component interactions, prefer Rust tests. Playwright is for visual verification and complete user flows.
[!note] We are still evaluating Playwright integration for broader testing use cases.
Unit tests are the foundation of our testing strategy. They test individual components in isolation, ensuring that each part of the codebase behaves as expected. Unit tests are fast to write and to execute, and they provide immediate feedback on the correctness of the code.
Integration tests verify the interaction between different components, ensuring that they work together as expected. Integration tests may check the integration between the business logic and the presentation layer, or between the view and the business logic.
End-to-end tests verify the entire system. They simulate real user interactions and check that the system behaves as expected.
Additional to the functional tests, we also need to implement performance tests.
We aim to follow the practice of Test Driven Development (TDD), where tests are written before the code is implemented. This helps to ensure that the code is testable and meets the requirements.
Onboarding
Rust
The main technology we use is Rust. A solid understanding of Rust is needed, though particularly the frontend work does not require deep knowledge of Rust.
Rust HTTP Server
We use Axum as our HTTP server.
Serialization and Deserialization
We use serde for serialization and deserialization of data.
Rust Templating
We use Maud as our templating engine for rendering HTML in Rust.
Maud is a macro-based templating engine that allows writing HTML directly in Rust code. It is similar to JSX, where the HTML is written inline with the Rust code. This approach provides strong type safety and excellent integration with Rust's ownership system.
Maud offers several advantages:
- Compile-time template checking
- Full Rust syntax support within templates
- Automatic HTML escaping
- No external template files to manage
- Better IDE support with syntax highlighting and autocomplete
Archtectural Design Patterns
We follow concepts such as Clean Architecture (there is also a book), Hexagonal Architecture or Onion Architecture. Familiarity with these concepts will be helpful.
Some of the patterns must be adapted to the idioms of Rust, but the general principles are the same.
Testing
We follow the Testing Pyramid approach to testing, the majority of tests are unit tests, with a smaller number of integration tests, and a few end-to-end tests.
Unit and integration tests are written in Rust, following the Rust testing best practices. End-to-end tests are written either in Rust or in JavaScript using Playwright.
We are looking into how to integrate Playwright into our setup. Playwright may be used for the following:
- End-to-end tests simulating entire user flows.
- Visual regression tests.
For single user interactions, we should not need Playwright. For these, we should use the Rust testing framework.
Domain Driven Design
We do not follow strict Domain Driven Design (DDD) principles, but we try to follow some of the concepts. In particular, we try to keep the language used in code aligned with the domain language.
Test Driven Development
We should absolutely do TDD and BDD.
Database
We are still evaluating the database to use.
For the initial development, we work with static content or JSON files.
Hypermedia
Instead of the traditional single-page application architecture, where the client is a JavaScript application that runs in the browser, and communicates with the server via JSON over HTTP, we are using a hypermedia approach.
In this approach, the server sends HTML pages or fragments to the client. The client simply renders the HTML, without much JavaScript. The client also does not need to maintain state, this is done by the server. Server and client keep a connection open, through which the server can send updates to the client, so called "server-sent events" (SSE), which guarantees responsiveness and interactivity.
This approach has several advantages:
- The client is much simpler, as it does not need to maintain state or manage complex interactions.
- The client does not have many security concerns, as the server is the source of truth and can control what is sent to the client.
- The server can update the client at any time, without the need for the client to poll for updates.
Furthermore, with this approach, we can share much of the code between the server and the client. With the UI-code living on the server, we can use the same language (Rust) for both server and client, and there is no strict separation between backend and frontend.
The hypermedia approach is not a new concept, it has been used in the past, but had been widely replaced by the single-page application approach. Lately, the hypermedia approach has been making a comeback.
This approach is best known from HTMX. HTMX is fairly well established and there is a lot of learning material available. Its major downside is that it is not sufficient for most use-cases, when used by itself. Most of the time, you need to combine it with other libraries, such as Alpine.js.
Rather than HTMX, we are using DataStar, which provides similar functionality, but is more compliant with web standards, and provides a more complete solution, so that we do not need to combine it with other libraries.
DataStar is a fairly new project, so there is not much learning material available yet. However, it is very similar to HTMX, so that most of the HTMX learning material can be applied to DataStar as well.
Design System
Rather than using a generic design system, and designing solutions on top of it ad hoc, we are using a purpose-built design system. This will help us to keep the design consistent and coherent, reduce complexity, and give our products a clearer brand identity.
This design system is based on the IBM Carbon Design System, but is customized to our needs.
Concept, Aim and Purpose
We build the DSP Design System on top of the Carbon Design System, because it communicates the reliability and trustworthiness that users expect from an archive. Furthermore, it is a well established design system, where concerns such as accessibility and usability have been well thought out.
There are several reasons why we do not use the Carbon Design System as is, and instead build our own design system on top of it:
- It is a customizeable, generic, general purpose design system.
As such, it provides a lot of options and flexibility but comes with a lot of complexity.
By customizing it to our needs, we can reduce complexity and limit it to our needs. By limiting optins (e.g. components, icons, tokens, etc.) to the ones we need, we can simplify design and ensure consistency in our products. - It is customizeable, but we do not need that flexibility.
By creating a purpose-built design system, we can bake our brand into it, and reduce complexity even further. - The official and community implementations of Carbon do not align with our tech stack.
By creating our own implementation, we can tailor it to our needs and move the implementation of individual components to dedicated design system work, rather than having to implement them in the context of project work.
The design system work will be done in close collaboration between Dev and PM, and is an up-fron investment that will improve the speed and quality of project work in the long run.
Implementation
It is part of the discovery process to define components needed for any project.
These are then implemented in the design system as individual, reuseable components. It should then be possible to import these components as Rust modules, and use them in the project code.
The details of the implementation depend on the rendering engine we decide to use.
Playground
We create a playground for the design system, where we can test and experiment with the components.
This playground is a Rust web server with TypeScript testing:
- Shell interface with component navigation
- Component isolation via iframe rendering
- Variant selection and theme switching
- Live reload via WebSocket
- TypeScript/Playwright tests for functional, accessibility, and visual regression testing
For each component, we create a page that shows the component in action, in all its variants and states. We also pages for compounds and patterns, where we show how to use the components together. Finally, we create sample pages that show how to use the components in a real-world scenario.