From 790f6f98890abe4f095c9bc0e3fcfc402ff6dab9 Mon Sep 17 00:00:00 2001 From: osenan Date: Thu, 24 Sep 2026 13:50:36 +0200 Subject: [PATCH 1/8] chore: add first version of CLAUDE.md --- CLAUDE.Rmd | 18 ++ CLAUDE.md | 532 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 550 insertions(+) create mode 100644 CLAUDE.Rmd create mode 100644 CLAUDE.md diff --git a/CLAUDE.Rmd b/CLAUDE.Rmd new file mode 100644 index 00000000..b1becc63 --- /dev/null +++ b/CLAUDE.Rmd @@ -0,0 +1,18 @@ +--- +title: "teal.code R Package Development Guide" +output: github_document +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set(echo = TRUE) +``` + + + +```{r teal_content, echo=FALSE, results='asis'} +common_claude_url <- "https://raw.githubusercontent.com/insightsengineering/teal/main/AGENTS.md" +common_claude_path <- tempfile() +download.file(url = common_claude_url, destfile = common_claude_path, quiet = TRUE) +common_claude_shared <- readLines(common_claude_path) +cat(common_claude_shared, sep = "\n") +``` \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..ff938f1f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,532 @@ +teal.code R Package Development Guide +================ + + + +# Teal Ecosystem R Package Development Guide + +## Introduction + +The teal framework uses shiny to create reproducible environments for +analysts. The ecosystem comprises several interconnected packages with +specific roles: + +### Core Packages + +- **teal** - The main framework package providing the application + structure +- **teal.code** - Bare code generation and evaluation ensuring + reproducibility +- **teal.data** - Data management and relationships between datasets + (contains sample data for ADaM datasets and default keys to merge ADaM + datasets) +- **teal.reporter** - Report generation functionality +- **teal.slice** - Data filtering capabilities for application +- **teal.widgets** - Reusable UI components +- **teal.logger** - Standardized logging across the framework +- **teal.picks** - Data selection and merging utilities using + `teal.data` objects +- **teal.transform** - Data transformation utilities (deprecated in + favor of teal.picks) + +### Module Packages + +- **teal.modules.general** (tmg) - General-purpose analysis modules +- **teal.modules.clinical** (tmc) - Clinical trial specific modules +- **teal.modules.hermes** - MultiAssayExperiment analysis modules +- **teal.goshawk** - Pharmacokinetics analysis modules +- **teal.osprey** - Advanced clinical analysis modules + +### Supporting Packages + +- **tern** - Statistical analysis functions +- **rtables** - Table creation and formatting +- **formatters** - Output formatting utilities +- **gtsummary** - Table creation and formatting + +**Key Principle**: Balance dependency value with features. Minimize +dependencies to packages not already in use within the ecosystem. + +## Package Structure and Organization + +### Standard Package Layout + +Follow the standard R package structure with teal-specific conventions: + +``` text +package_name/ +├── .github/workflows/ # CI/CD workflows (use r.pkg.template) +├── R/ # R source code +├── tests/testthat/ # Unit tests +├── man/ # Documentation +├── vignettes/ # Long-form documentation +├── inst/ # Package assets +├── DESCRIPTION # Package metadata +├── NAMESPACE # Exports and imports +├── NEWS.md # Change log +├── README.md # Package overview +├── _pkgdown.yml # Documentation website config +├── .lintr # Linting configuration +└── .Rbuildignore # Build exclusions +``` + +### Naming Conventions + +- **Package names**: Use `teal.` prefix for ecosystem packages (e.g., + `teal.widgets`) +- **Function names**: Use `snake_case` consistently +- **Class names**: Use `PascalCase` (e.g., `TealAppDriver`) +- **Module functions**: Prefix UI functions with `ui_` and server + functions with `srv_` +- **Internal functions**: Use descriptive names without export + +### File Organization + +- **One main function per file** when the function is substantial +- **Group related utilities** in shared files (e.g., `utils.R`, + `validations.R`) +- **Module files**: Use pattern `module_.R` for shiny modules +- **Helper functions**: Prefix with the main function they support + +## Code Style and Standards + +### Code Quality + +- **Run pre-commit hooks**: Always run `pre-commit run --all-files` + before committing. Fix any issues it reports - the error messages are + informative and will guide you. +- **Follow tidyverse style**: General R code style follows the tidyverse + style guide. +- **Documentation**: All exported functions must have roxygen2 + documentation. Run `devtools::document()` to update documentation. + +### Formatting + +Formatting rules are configured in the `.lintr` file. + +### Teal-Specific Conventions + +- **Naming**: Follow the naming conventions outlined in the Package + Structure section + +## Dependencies and Imports + +### Dependency Management + +- **Minimize dependencies**: Only add dependencies that provide + significant value +- **Version constraints**: Specify minimum versions for critical + dependencies +- **Ecosystem coherence**: Prefer packages already used within teal + ecosystem + +``` r +# DESCRIPTION example +Depends: + R (>= 4.1), + shiny (>= 1.8.1) +Imports: + checkmate (>= 2.1.0), + rlang (>= 1.0.0), + teal.widgets (>= 0.5.0) +Suggests: + testthat (>= 3.2.0), + knitr, + rmarkdown +``` + +### Import Best Practices + +``` r +# In NAMESPACE, prefer specific imports over full package imports +#' @importFrom shiny moduleServer NS tagList +#' @importFrom checkmate assert_character assert_function +#' @import teal.data # Only for core teal packages + +# In code, use explicit namespacing for clarity when appropriate +checkmate::assert_string(label) +``` + +## Modules Development + +### Module features + +Each module should produce one or more Table, Listing, or Graph (TLG): + +- **Reproducibility**: All code being executed to generate TLGs should + be run using `teal_data` and `within()` / `teal.code::eval_code()` + - At the end of the module this object should be returned to enable + Reporter and “Show R code” functionalities +- **User Parameters**: Configurable inputs via `teal.picks::picks()` for + flexible data selection +- **Transformators**: Optional pre-processing functions that derive + variables and validate data before analysis +- **Decorators**: Optional post-processing functions that customize + output presentation (titles, legends, annotations) + +### Module Architecture + +Teal modules follow a specific pattern with UI and server components: + +``` r +# UI Function +ui_example_module <- function(id, var_x, var_y, decorators) { + ns <- shiny::NS(id) + select_decorators <- getFromNamespace("select_decorators", "teal") # import from teal internal functions + + shiny::tagList( + # Input controls + teal.widgets::standard_layout( + # Output displays + output = teal.widgets::white_small_well( + teal::ui_transform_teal_data("decorator_table", select_decorators(decorators, "plot")), + teal::ui_transform_teal_data("decorator_table", select_decorators(decorators, "table")), + shiny::tags$h4("Results"), + shiny::plotOutput(ns("plot")), + shiny::tags$h4("Summary data"), + gt::gt_output(ns("table")) + ), + # Encoding panel + encoding = shiny::tags$div( + shiny::tags$label("Encodings", class = "text-primary"), + shiny::tags$br(), + shiny::tags$div( + shiny::tags$strong("Select X-Axis Variable"), + teal.picks::picks_ui(ns("var_x"), var_x) + ), + shiny::tags$div( + shiny::tags$strong("Select Y-Axis Variable"), + teal.picks::picks_ui(ns("var_y"), var_y) + ) + ) + ) + ) +} + +# Server Function +srv_example_module <- function(id, data, var_x, var_y, decorators) { + checkmate::assert_string(id) + checkmate::assert_class(data, "reactive") + + select_decorators <- getFromNamespace("select_decorators", "teal") # import from teal internal functions + shiny::moduleServer(id, function(input, output, session) { + selectors <- teal.picks::picks_srv("picks", picks = list(var_x = var_x, var_y = var_y), data = data) + merged <- teal.picks::merge_srv( + "merge_picks", + data = data, + selectors = selectors, + output_name = "anl", + join_fun = "dplyr::inner_join" + ) + # Data preparation + validated_q <- shiny::reactive({ + shiny::validate( + teal::need_input( + inputId = "var_x-variables-selected", + condition = length(selectors$var_x()$variables$selected) > 0, + message = "X-Axis Variable must be selected" + ), + teal::need_input( + inputId = "var_y-variables-selected", + condition = length(selectors$var_y()$variables$selected) > 0, + message = "Y-Axis Variable must be selected" + ) + ) + shiny::validate( + teal::need_input( + inputId = c("var_x-variables-selected", "var_y-variables-selected"), + condition = !any(selectors$var_x()$variables$selected %in% selectors$var_y()$variables$selected), + message = "X-axis variable and Y-axis variable must be different" + ) + ) + q <- merged$data() + teal.reporter::teal_card(q) <- c(teal.reporter::teal_card(q), "## Module's output") + q + }) + + # Generate plot inside qenv + qenv_plot <- reactive({ + within(validated_q(), { + plot <- ggplot2::ggplot(anl) + + ggplot2::geom_point(ggplot2::aes(x = env_var_x, y = env_var_y)) + }, env_var_x = as.name(merged$variables()$var_x), env_var_y = as.name(merged$variables()$var_y)) + }) + decorated_plot <- teal::srv_transform_teal_data( + "decorator_table", + qenv_plot, + select_decorators(decorators, "plot"), + expr = quote(plot) + ) + + qenv_table <- reactive({ + within(validated_q(), { + table <- gtsummary::tbl_summary(anl, by = env_var_x, missing = "no") + }, env_var_x = as.name(merged$variables()$var_x), env_var_y = as.name(merged$variables()$var_y)) + }) + decorated_table <- teal::srv_transform_teal_data( + "decorator_table", + qenv_table, + select_decorators(decorators, "table"), + expr = quote(table) + ) + + # Output rendering: use ggplot2 for visualizations + output$plot <- shiny::renderPlot(decorated_plot()[["plot"]]) + output$table <- gt::render_gt(expr = gtsummary::as_gt(decorated_table()[["table"]])) + # Return reactive + + reactive(c(decorated_plot(), decorated_table())) + }) +} + +tm_example_module <- function( + label = "Example Module", + var_x = teal.picks::picks(teal.picks::datasets(), teal.picks::variables(is.numeric, selected = 1L)), + var_y = teal.picks::picks(teal.picks::datasets(), teal.picks::variables(is.numeric, selected = 2L)), + decorators = list(), + transformators = list() +) { + checkmate::assert_string(label) + checkmate::assert_class(var_x, "picks") + checkmate::assert_class(var_y, "picks") + checkmate::assert_list(transformators, types = "teal_transform_module") + args <- list(var_x = var_x, var_y = var_y, decorators = decorators) + teal::module( + label = label, + server = srv_example_module, + ui = ui_example_module, + ui_args = args[names(args) %in% names(formals(ui_example_module))], + server_args = args[names(args) %in% names(formals(srv_example_module))], + transformators = transformators + ) +} +``` + +### Code Style for Modules + +- **Use tidyverse style**: Write clear, readable code using dplyr, + ggplot2 patterns +- **Use maggritr pipes in reproducible execution**: For code executed + for `teal_data`/`qenv` data objects with `eval_code()` and `within()` +- **Prefer ggplot2**: For all visualizations over base R plotting +- **Use gt and gtsummary**: For statistical tables and summaries +- **Error handling**: Implement proper validation using `checkmate` and + `shiny::validate(teal::need_input(...))` + +``` r +# Good: Clear data manipulation +plot_data <- data %>% + dplyr::filter(!is.na(variable)) %>% + dplyr::group_by(category) %>% + dplyr::summarise( + mean_value = mean(value), + n = dplyr::n(), + .groups = "drop" + ) + +# Good: Descriptive ggplot2 code +ggplot2::ggplot(plot_data, ggplot2::aes(x = category, y = mean_value)) + + ggplot2::geom_col(fill = "steelblue") + + ggplot2::labs( + title = "Mean Values by Category", + x = "Category", + y = "Mean Value" + ) + + ggplot2::theme_minimal() +``` + +## Testing Framework + +### Testing Philosophy + +- **Test public functions only**: Internal utilities should be tested + through public interfaces +- **Precise, focused tests**: Each test should verify one specific + behavior +- **High coverage**: Maintain at least 80% test coverage as measured by + `covr` +- **Integration over units**: Test realistic usage patterns +- **Test Dependencies**.: Add + `testthat::skip_if_not_installed(package_name)` only for dependencies + in SUGGESTS or related to tests cases + +### Test Structure + +Follow the established patterns from `test-module_teal.R`: + +``` r +# Test organization +testthat::test_that("function_name works with valid inputs", { + # Setup + test_data <- data.frame(x = 1:10, y = rnorm(10)) + + # Execution + result <- function_name(test_data) + + # Verification - one expectation per test preferably + testthat::expect_s3_class(result, "data.frame") +}) + +testthat::test_that("function_name handles edge cases", { + # Test empty input + testthat::expect_error( + function_name(data.frame()), + "Input data cannot be empty" + ) +}) + +testthat::test_that("function_name validates input types", { + # Test invalid input type + testthat::expect_error( + function_name("not a data frame"), + class = "checkmate_error" + ) +}) +``` + +### Shiny Module Testing + +- **Server functions**: Test with `shiny::testServer()` +- **UI functions**: Test basic usage with regular testing (class checks, + error generation, snapshots, regexp search). Test UI scenarios and + interactions with `TealAppDriver` (based on `shinytest2::AppDriver`) + for integration testing +- **Reactive behavior**: Test reactive chains and side effects + +``` r +testthat::test_that("srv_my_module processes data correctly", { + # Test server logic + shiny::testServer( + app = srv_my_module, + args = list( + data = reactive(test_data), + filter_panel_api = NULL + ), + expr = { + # Test reactive computations + result <- processed_data() + testthat::expect_s3_class(result, "teal_data") + } + ) +}) + +testthat::test_that("my_module UI renders correctly", { + # Integration test with TealAppDriver + app <- init( + data = teal_data(mtcars = mtcars), + modules = my_module() + ) + + driver <- TealAppDriver$new(app) + withr::defer(driver$stop()) + driver$navigate_teal_tab("My Module") + + # Test UI elements are present + driver$expect_visible("#plot") +}) +``` + +### Test Organization and Naming + +- **One test file per R file**: `test-module_example.R` for + `module_example.R` +- **Descriptive test names**: Clearly describe what is being tested +- **End to end test names**: `test-shinytest2-module_example.R` for + `module_example.R` +- **Logical grouping**: Group related tests using `describe()` when + beneficial +- **Test data**: Create minimal test datasets, avoid external + dependencies + +## Documentation and Communication + +### Package Documentation + +- **README.md**: Clear overview, installation, basic usage examples +- **Vignettes**: Comprehensive guides for complex functionality +- **Function documentation**: All exported functions must have roxygen2 + documentation +- **NEWS.md**: Detailed changelog following semantic versioning + +### Website Generation + +Use `_pkgdown.yml` for documentation websites: + +``` yaml +url: https://insightsengineering.github.io/package.name + +template: + package: nesttemplate + +reference: + - title: "Main Functions" + contents: + - init + - module + - title: "Helper Functions" + contents: + - starts_with("validate_") +``` + +### Version Management + +Do not change versions on your own. + +## CI/CD and Development Workflow + +### GitHub Workflows + +Use `r.pkg.template` workflows for consistency: + +- **check.yaml**: R CMD check, unit tests, coverage +- **docs.yaml**: Documentation building and deployment +- **audit.yaml**: Security and dependency auditing +- **pkgdown.yaml**: Website generation + +### Pre-commit Hooks + +**Always run pre-commit before committing code**: + +``` bash +pre-commit run --all-files +``` + +Fix any issues that pre-commit reports. The error messages are +informative and will guide you on what needs to be fixed. Pre-commit +automatically checks code style, documentation, linting, and other +quality issues. + +### Dependency Management with Staged Dependencies + +`staged_dependencies.yaml` is an old artifact. Ignore it. + +## Quality Assurance + +### Code Quality Metrics + +- **Test Coverage**: ≥80% line coverage +- **Linting**: No lint violations using configured `.lintr` +- **Documentation**: 100% of exports documented +- **Dependencies**: Minimal and justified dependencies only + +### Code Review Process + +- **Pull Request Reviews**: All changes require review +- **Automated Checks**: CI must pass before merging +- **Breaking Changes**: Require special consideration and communication +- **Documentation Updates**: Must accompany functional changes + +### Performance Considerations + +- **Shiny Reactivity**: Minimize unnecessary reactive computations +- **Data Processing**: Use efficient data manipulation patterns +- **Memory Usage**: Consider memory implications for large datasets +- **Loading Time**: Optimize package loading and module initialization + +## Maintenance Guidelines + +- **Long-term Support**: Maintain backward compatibility when possible +- **Deprecation**: Use `lifecycle` package for function deprecation + +This guide ensures consistency, quality, and maintainability across the +teal ecosystem while following R community best practices. From f1a471b47327d874bcbf74f506a6ace842503c41 Mon Sep 17 00:00:00 2001 From: osenan Date: Fri, 25 Sep 2026 13:03:18 +0200 Subject: [PATCH 2/8] docs: add full intstructions to CLAUDE.Rmd --- CLAUDE.Rmd | 41 ++++- CLAUDE.md | 489 ++++++++++------------------------------------------- 2 files changed, 126 insertions(+), 404 deletions(-) diff --git a/CLAUDE.Rmd b/CLAUDE.Rmd index b1becc63..4bb36d5b 100644 --- a/CLAUDE.Rmd +++ b/CLAUDE.Rmd @@ -7,12 +7,49 @@ output: github_document knitr::opts_chunk$set(echo = TRUE) ``` - +## Package Overview + +`teal.code` is an R package for storing code and its execution environment in objects called `qenv`. +It supports reproducible code execution in teal Shiny applications. Developers can inspect and modify `qenv` objects, +providing full control over the stored code and environment. These objects also capture warnings and errors automatically. + +## Development Context + +The teal framework uses Shiny to create reproducible environments for data analysis. Within this framework, `teal.code` +manages code and its execution environment. Related packages include: + +- `teal.reporter`: Integrates reports with reproducible code in teal applications. Its reporter functions accept `qenv` + objects as inputs. +- `teal`: Provides the core architecture for teal Shiny applications. Teal modules should use `qenv` objects to ensure + that code changes are recorded and reproducible. + +For an introduction to `qenv` and its related functions and methods, see @vignettes/qenv.Rmd. + +### Workflows + +- When creating a `qenv`, favor readability over concise code. +- For simple `qenv` objects, favor `within()`, as it supports more readable code. +- When creating a `qenv` that uses functions with side effects, such as `runif()`, always use `eval_code()`. +- For a complex `qenv`, consider dividing the work into smaller `qenv` objects that encapsulate distinct logical steps. +- For a long or complex `qenv`, choose between `eval_code()` and `within()` based on: + - the number of external variables added to the `qenv`; a larger number favors `eval_code()`; + - how often the same external variable is used; greater repetition favors `within()`. +- When fixing bugs or adding features, avoid changing the core principles of `qenv`. Check whether an issue already + exists. If the expected behavior is unclear, request that an issue be created before making changes that could have + unexpected consequences. ```{r teal_content, echo=FALSE, results='asis'} -common_claude_url <- "https://raw.githubusercontent.com/insightsengineering/teal/main/AGENTS.md" +# Append common shared instructions from teal framework +common_claude_url <- "https://raw.githubusercontent.com/insightsengineering/teal/refs/heads/add_common_claude%40main/SHARED_CLAUDE.md" # nolint: line_length_linter. common_claude_path <- tempfile() download.file(url = common_claude_url, destfile = common_claude_path, quiet = TRUE) common_claude_shared <- readLines(common_claude_path) +# remove CI gitlab content and github section comments +gitlab_starts <- grep("gitlab starts here", common_claude_shared) +gitlab_ends <- grep("gitlab ends here", common_claude_shared) +common_claude_shared <- common_claude_shared[-1*seq(gitlab_starts, gitlab_ends)] +github_starts <- grep("github starts here", common_claude_shared) +github_ends <- grep("github ends here", common_claude_shared) +common_claude_shared <- common_claude_shared[-1*c(github_starts, github_ends)] cat(common_claude_shared, sep = "\n") ``` \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index ff938f1f..42bc6b7f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,79 +1,76 @@ teal.code R Package Development Guide ================ - - -# Teal Ecosystem R Package Development Guide - -## Introduction - -The teal framework uses shiny to create reproducible environments for -analysts. The ecosystem comprises several interconnected packages with -specific roles: - -### Core Packages - -- **teal** - The main framework package providing the application - structure -- **teal.code** - Bare code generation and evaluation ensuring - reproducibility -- **teal.data** - Data management and relationships between datasets - (contains sample data for ADaM datasets and default keys to merge ADaM - datasets) -- **teal.reporter** - Report generation functionality -- **teal.slice** - Data filtering capabilities for application -- **teal.widgets** - Reusable UI components -- **teal.logger** - Standardized logging across the framework -- **teal.picks** - Data selection and merging utilities using - `teal.data` objects -- **teal.transform** - Data transformation utilities (deprecated in - favor of teal.picks) - -### Module Packages - -- **teal.modules.general** (tmg) - General-purpose analysis modules -- **teal.modules.clinical** (tmc) - Clinical trial specific modules -- **teal.modules.hermes** - MultiAssayExperiment analysis modules -- **teal.goshawk** - Pharmacokinetics analysis modules -- **teal.osprey** - Advanced clinical analysis modules - -### Supporting Packages - -- **tern** - Statistical analysis functions -- **rtables** - Table creation and formatting -- **formatters** - Output formatting utilities -- **gtsummary** - Table creation and formatting - -**Key Principle**: Balance dependency value with features. Minimize -dependencies to packages not already in use within the ecosystem. +## Package Overview + +`teal.code` is an R package for storing code and its execution +environment in objects called `qenv`. It supports reproducible code +execution in teal Shiny applications. Developers can inspect and modify +`qenv` objects, providing full control over the stored code and +environment. These objects also capture warnings and errors +automatically. + +## Development Context + +The teal framework uses Shiny to create reproducible environments for +data analysis. Within this framework, `teal.code` manages code and its +execution environment. Related packages include: + +- `teal.reporter`: Integrates reports with reproducible code in teal + applications. Its reporter functions accept `qenv` objects as inputs. +- `teal`: Provides the core architecture for teal Shiny applications. + Teal modules should use `qenv` objects to ensure that code changes are + recorded and reproducible. + +For an introduction to `qenv` and its related functions and methods, see +@vignettes/qenv.Rmd. + +### Workflows + +- When creating a `qenv`, favor readability over concise code. +- For simple `qenv` objects, favor `within()`, as it supports more + readable code. +- When creating a `qenv` that uses functions with side effects, such as + `runif()`, always use `eval_code()`. +- For a complex `qenv`, consider dividing the work into smaller `qenv` + objects that encapsulate distinct logical steps. +- For a long or complex `qenv`, choose between `eval_code()` and + `within()` based on: - the number of external variables added to the + `qenv`; a larger number favors `eval_code()`; - how often the same + external variable is used; greater repetition favors `within()`. +- When fixing bugs or adding features, avoid changing the core + principles of `qenv`. Check whether an issue already exists. If the + expected behavior is unclear, request that an issue be created before + making changes that could have unexpected consequences. + +This package is part of the teal framework. The following configuration +applies to all packages within the teal framework: ## Package Structure and Organization -### Standard Package Layout +### Key Directories Follow the standard R package structure with teal-specific conventions: ``` text package_name/ -├── .github/workflows/ # CI/CD workflows (use r.pkg.template) -├── R/ # R source code -├── tests/testthat/ # Unit tests -├── man/ # Documentation -├── vignettes/ # Long-form documentation -├── inst/ # Package assets -├── DESCRIPTION # Package metadata -├── NAMESPACE # Exports and imports -├── NEWS.md # Change log -├── README.md # Package overview -├── _pkgdown.yml # Documentation website config -├── .lintr # Linting configuration -└── .Rbuildignore # Build exclusions +├── .gitlab-ci.yml # CI/CD workflows +├── R/ # R source code +├── tests/testthat/ # Unit tests using testthat +├── vignettes/ # Long-form documentation +├── inst/ # Package assets +├── CLAUDE.md # Development guide for AI agents (this file) +├── DESCRIPTION # Package metadata +├── NAMESPACE # Exports and imports automa +├── NEWS.md # Change log +├── README.md # Package overview +├── _pkgdown.yml # Documentation website config +├── .lintr # Linting configuration +└── .Rbuildignore # Build exclusions ``` ### Naming Conventions -- **Package names**: Use `teal.` prefix for ecosystem packages (e.g., - `teal.widgets`) - **Function names**: Use `snake_case` consistently - **Class names**: Use `PascalCase` (e.g., `TealAppDriver`) - **Module functions**: Prefix UI functions with `ui_` and server @@ -85,29 +82,22 @@ package_name/ - **One main function per file** when the function is substantial - **Group related utilities** in shared files (e.g., `utils.R`, `validations.R`) -- **Module files**: Use pattern `module_.R` for shiny modules +- **Module files**: Use pattern `tm_.R` for teal modules - **Helper functions**: Prefix with the main function they support ## Code Style and Standards ### Code Quality -- **Run pre-commit hooks**: Always run `pre-commit run --all-files` +- **Run `pre-commit` hooks**: Always run `pre-commit run --all-files` before committing. Fix any issues it reports - the error messages are - informative and will guide you. -- **Follow tidyverse style**: General R code style follows the tidyverse - style guide. -- **Documentation**: All exported functions must have roxygen2 + informative and will guide you. It automatically checks code style, + documentation, linting, and other quality issues. +- **Follow `tidyverse` style**: General R code style follows the + `tidyverse` style guide. +- **Documentation**: All exported functions must have `roxygen2` documentation. Run `devtools::document()` to update documentation. - -### Formatting - -Formatting rules are configured in the `.lintr` file. - -### Teal-Specific Conventions - -- **Naming**: Follow the naming conventions outlined in the Package - Structure section +- **Formatting** rules are configured in the `.lintr` file. ## Dependencies and Imports @@ -120,221 +110,22 @@ Formatting rules are configured in the `.lintr` file. - **Ecosystem coherence**: Prefer packages already used within teal ecosystem -``` r -# DESCRIPTION example -Depends: - R (>= 4.1), - shiny (>= 1.8.1) -Imports: - checkmate (>= 2.1.0), - rlang (>= 1.0.0), - teal.widgets (>= 0.5.0) -Suggests: - testthat (>= 3.2.0), - knitr, - rmarkdown -``` - ### Import Best Practices -``` r -# In NAMESPACE, prefer specific imports over full package imports -#' @importFrom shiny moduleServer NS tagList -#' @importFrom checkmate assert_character assert_function -#' @import teal.data # Only for core teal packages - -# In code, use explicit namespacing for clarity when appropriate -checkmate::assert_string(label) -``` - -## Modules Development - -### Module features - -Each module should produce one or more Table, Listing, or Graph (TLG): - -- **Reproducibility**: All code being executed to generate TLGs should - be run using `teal_data` and `within()` / `teal.code::eval_code()` - - At the end of the module this object should be returned to enable - Reporter and “Show R code” functionalities -- **User Parameters**: Configurable inputs via `teal.picks::picks()` for - flexible data selection -- **Transformators**: Optional pre-processing functions that derive - variables and validate data before analysis -- **Decorators**: Optional post-processing functions that customize - output presentation (titles, legends, annotations) - -### Module Architecture - -Teal modules follow a specific pattern with UI and server components: - -``` r -# UI Function -ui_example_module <- function(id, var_x, var_y, decorators) { - ns <- shiny::NS(id) - select_decorators <- getFromNamespace("select_decorators", "teal") # import from teal internal functions - - shiny::tagList( - # Input controls - teal.widgets::standard_layout( - # Output displays - output = teal.widgets::white_small_well( - teal::ui_transform_teal_data("decorator_table", select_decorators(decorators, "plot")), - teal::ui_transform_teal_data("decorator_table", select_decorators(decorators, "table")), - shiny::tags$h4("Results"), - shiny::plotOutput(ns("plot")), - shiny::tags$h4("Summary data"), - gt::gt_output(ns("table")) - ), - # Encoding panel - encoding = shiny::tags$div( - shiny::tags$label("Encodings", class = "text-primary"), - shiny::tags$br(), - shiny::tags$div( - shiny::tags$strong("Select X-Axis Variable"), - teal.picks::picks_ui(ns("var_x"), var_x) - ), - shiny::tags$div( - shiny::tags$strong("Select Y-Axis Variable"), - teal.picks::picks_ui(ns("var_y"), var_y) - ) - ) - ) - ) -} - -# Server Function -srv_example_module <- function(id, data, var_x, var_y, decorators) { - checkmate::assert_string(id) - checkmate::assert_class(data, "reactive") - - select_decorators <- getFromNamespace("select_decorators", "teal") # import from teal internal functions - shiny::moduleServer(id, function(input, output, session) { - selectors <- teal.picks::picks_srv("picks", picks = list(var_x = var_x, var_y = var_y), data = data) - merged <- teal.picks::merge_srv( - "merge_picks", - data = data, - selectors = selectors, - output_name = "anl", - join_fun = "dplyr::inner_join" - ) - # Data preparation - validated_q <- shiny::reactive({ - shiny::validate( - teal::need_input( - inputId = "var_x-variables-selected", - condition = length(selectors$var_x()$variables$selected) > 0, - message = "X-Axis Variable must be selected" - ), - teal::need_input( - inputId = "var_y-variables-selected", - condition = length(selectors$var_y()$variables$selected) > 0, - message = "Y-Axis Variable must be selected" - ) - ) - shiny::validate( - teal::need_input( - inputId = c("var_x-variables-selected", "var_y-variables-selected"), - condition = !any(selectors$var_x()$variables$selected %in% selectors$var_y()$variables$selected), - message = "X-axis variable and Y-axis variable must be different" - ) - ) - q <- merged$data() - teal.reporter::teal_card(q) <- c(teal.reporter::teal_card(q), "## Module's output") - q - }) - - # Generate plot inside qenv - qenv_plot <- reactive({ - within(validated_q(), { - plot <- ggplot2::ggplot(anl) + - ggplot2::geom_point(ggplot2::aes(x = env_var_x, y = env_var_y)) - }, env_var_x = as.name(merged$variables()$var_x), env_var_y = as.name(merged$variables()$var_y)) - }) - decorated_plot <- teal::srv_transform_teal_data( - "decorator_table", - qenv_plot, - select_decorators(decorators, "plot"), - expr = quote(plot) - ) - - qenv_table <- reactive({ - within(validated_q(), { - table <- gtsummary::tbl_summary(anl, by = env_var_x, missing = "no") - }, env_var_x = as.name(merged$variables()$var_x), env_var_y = as.name(merged$variables()$var_y)) - }) - decorated_table <- teal::srv_transform_teal_data( - "decorator_table", - qenv_table, - select_decorators(decorators, "table"), - expr = quote(table) - ) - - # Output rendering: use ggplot2 for visualizations - output$plot <- shiny::renderPlot(decorated_plot()[["plot"]]) - output$table <- gt::render_gt(expr = gtsummary::as_gt(decorated_table()[["table"]])) - # Return reactive - - reactive(c(decorated_plot(), decorated_table())) - }) -} - -tm_example_module <- function( - label = "Example Module", - var_x = teal.picks::picks(teal.picks::datasets(), teal.picks::variables(is.numeric, selected = 1L)), - var_y = teal.picks::picks(teal.picks::datasets(), teal.picks::variables(is.numeric, selected = 2L)), - decorators = list(), - transformators = list() -) { - checkmate::assert_string(label) - checkmate::assert_class(var_x, "picks") - checkmate::assert_class(var_y, "picks") - checkmate::assert_list(transformators, types = "teal_transform_module") - args <- list(var_x = var_x, var_y = var_y, decorators = decorators) - teal::module( - label = label, - server = srv_example_module, - ui = ui_example_module, - ui_args = args[names(args) %in% names(formals(ui_example_module))], - server_args = args[names(args) %in% names(formals(srv_example_module))], - transformators = transformators - ) -} -``` +Avoid importing package functions via roxygen2 (`#' @import pkg`)tags in +favor of explicit namespacing for clarity when appropriate. When needed +prefer specific imports over full package imports. ### Code Style for Modules -- **Use tidyverse style**: Write clear, readable code using dplyr, - ggplot2 patterns -- **Use maggritr pipes in reproducible execution**: For code executed +- **Use `tidyverse` style**: Write clear, readable code using `dplyr`, + `ggplot2` patterns +- **Use `magrittr` pipes in reproducible execution**: For code executed for `teal_data`/`qenv` data objects with `eval_code()` and `within()` -- **Prefer ggplot2**: For all visualizations over base R plotting -- **Use gt and gtsummary**: For statistical tables and summaries +- **Use crane and gtsummary**: For statistical tables and summaries - **Error handling**: Implement proper validation using `checkmate` and `shiny::validate(teal::need_input(...))` -``` r -# Good: Clear data manipulation -plot_data <- data %>% - dplyr::filter(!is.na(variable)) %>% - dplyr::group_by(category) %>% - dplyr::summarise( - mean_value = mean(value), - n = dplyr::n(), - .groups = "drop" - ) - -# Good: Descriptive ggplot2 code -ggplot2::ggplot(plot_data, ggplot2::aes(x = category, y = mean_value)) + - ggplot2::geom_col(fill = "steelblue") + - ggplot2::labs( - title = "Mean Values by Category", - x = "Category", - y = "Mean Value" - ) + - ggplot2::theme_minimal() -``` - ## Testing Framework ### Testing Philosophy @@ -350,82 +141,15 @@ ggplot2::ggplot(plot_data, ggplot2::aes(x = category, y = mean_value)) + `testthat::skip_if_not_installed(package_name)` only for dependencies in SUGGESTS or related to tests cases -### Test Structure - -Follow the established patterns from `test-module_teal.R`: - -``` r -# Test organization -testthat::test_that("function_name works with valid inputs", { - # Setup - test_data <- data.frame(x = 1:10, y = rnorm(10)) - - # Execution - result <- function_name(test_data) - - # Verification - one expectation per test preferably - testthat::expect_s3_class(result, "data.frame") -}) - -testthat::test_that("function_name handles edge cases", { - # Test empty input - testthat::expect_error( - function_name(data.frame()), - "Input data cannot be empty" - ) -}) - -testthat::test_that("function_name validates input types", { - # Test invalid input type - testthat::expect_error( - function_name("not a data frame"), - class = "checkmate_error" - ) -}) -``` - ### Shiny Module Testing - **Server functions**: Test with `shiny::testServer()` - **UI functions**: Test basic usage with regular testing (class checks, error generation, snapshots, regexp search). Test UI scenarios and - interactions with `TealAppDriver` (based on `shinytest2::AppDriver`) - for integration testing + interactions with `teal::TealAppDriver` (based on + `shinytest2::AppDriver`) for integration testing - **Reactive behavior**: Test reactive chains and side effects -``` r -testthat::test_that("srv_my_module processes data correctly", { - # Test server logic - shiny::testServer( - app = srv_my_module, - args = list( - data = reactive(test_data), - filter_panel_api = NULL - ), - expr = { - # Test reactive computations - result <- processed_data() - testthat::expect_s3_class(result, "teal_data") - } - ) -}) - -testthat::test_that("my_module UI renders correctly", { - # Integration test with TealAppDriver - app <- init( - data = teal_data(mtcars = mtcars), - modules = my_module() - ) - - driver <- TealAppDriver$new(app) - withr::defer(driver$stop()) - driver$navigate_teal_tab("My Module") - - # Test UI elements are present - driver$expect_visible("#plot") -}) -``` - ### Test Organization and Naming - **One test file per R file**: `test-module_example.R` for @@ -442,63 +166,27 @@ testthat::test_that("my_module UI renders correctly", { ### Package Documentation -- **README.md**: Clear overview, installation, basic usage examples +- **`README.md`**: Clear overview, installation, basic usage examples - **Vignettes**: Comprehensive guides for complex functionality -- **Function documentation**: All exported functions must have roxygen2 - documentation -- **NEWS.md**: Detailed changelog following semantic versioning - -### Website Generation - -Use `_pkgdown.yml` for documentation websites: - -``` yaml -url: https://insightsengineering.github.io/package.name - -template: - package: nesttemplate +- **Function documentation**: All exported functions must have + `roxygen2` documentation +- **`NEWS.md`**: Detailed changelog following semantic versioning -reference: - - title: "Main Functions" - contents: - - init - - module - - title: "Helper Functions" - contents: - - starts_with("validate_") -``` - -### Version Management +### Package Version Management -Do not change versions on your own. +Do not change versions on your own. There is a CI/CD workflow that +manages the versions automatically on the `main` branch. ## CI/CD and Development Workflow ### GitHub Workflows -Use `r.pkg.template` workflows for consistency: - -- **check.yaml**: R CMD check, unit tests, coverage -- **docs.yaml**: Documentation building and deployment -- **audit.yaml**: Security and dependency auditing -- **pkgdown.yaml**: Website generation - -### Pre-commit Hooks - -**Always run pre-commit before committing code**: +Use r.pkg.template workflows for consistency: -``` bash -pre-commit run --all-files -``` - -Fix any issues that pre-commit reports. The error messages are -informative and will guide you on what needs to be fixed. Pre-commit -automatically checks code style, documentation, linting, and other -quality issues. - -### Dependency Management with Staged Dependencies - -`staged_dependencies.yaml` is an old artifact. Ignore it. +- `check.yaml`: R CMD check, unit tests, coverage +- `docs.yaml`: Documentation building and deployment +- `audit.yaml`: Security and dependency auditing +- `pkgdown.yaml`: Website generation ## Quality Assurance @@ -527,6 +215,3 @@ quality issues. - **Long-term Support**: Maintain backward compatibility when possible - **Deprecation**: Use `lifecycle` package for function deprecation - -This guide ensures consistency, quality, and maintainability across the -teal ecosystem while following R community best practices. From 2e980f177cd0009f4a9a3699d7c09ec3800b7018 Mon Sep 17 00:00:00 2001 From: osenan Date: Fri, 25 Sep 2026 13:08:03 +0200 Subject: [PATCH 3/8] chore: update config for verified commits From e70a4fb219b6d9a0cb15e1e876e7cda57118555a Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 11:10:34 +0000 Subject: [PATCH 4/8] [skip style] [skip vbump] Restyle files --- CLAUDE.Rmd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CLAUDE.Rmd b/CLAUDE.Rmd index 4bb36d5b..aa67569a 100644 --- a/CLAUDE.Rmd +++ b/CLAUDE.Rmd @@ -47,9 +47,9 @@ common_claude_shared <- readLines(common_claude_path) # remove CI gitlab content and github section comments gitlab_starts <- grep("gitlab starts here", common_claude_shared) gitlab_ends <- grep("gitlab ends here", common_claude_shared) -common_claude_shared <- common_claude_shared[-1*seq(gitlab_starts, gitlab_ends)] +common_claude_shared <- common_claude_shared[-1 * seq(gitlab_starts, gitlab_ends)] github_starts <- grep("github starts here", common_claude_shared) github_ends <- grep("github ends here", common_claude_shared) -common_claude_shared <- common_claude_shared[-1*c(github_starts, github_ends)] +common_claude_shared <- common_claude_shared[-1 * c(github_starts, github_ends)] cat(common_claude_shared, sep = "\n") -``` \ No newline at end of file +``` From 1fafbb6093286ae62b518df69f3c010574691439 Mon Sep 17 00:00:00 2001 From: osenan Date: Fri, 25 Sep 2026 13:17:32 +0200 Subject: [PATCH 5/8] chore: update config for verified commits From d4b42137ad2d4cf9f6fb3a291c12a06c7cc2f163 Mon Sep 17 00:00:00 2001 From: osenan Date: Fri, 25 Sep 2026 13:25:54 +0200 Subject: [PATCH 6/8] chore: include the CLAUDE.md in the Rbuildignore --- .Rbuildignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.Rbuildignore b/.Rbuildignore index 5164b2a4..9803b731 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -8,6 +8,7 @@ SECURITY.md ^Makefile$ ^Meta$ ^TODO\.md$ +CLAUDE.md ^[^/]+\.R$ ^[^/]+\.Rmd$ ^[^/]+\.html$ From 36461ec5a867f0fa80d0b68756646db5340d92cc Mon Sep 17 00:00:00 2001 From: osenan Date: Fri, 25 Sep 2026 13:35:05 +0200 Subject: [PATCH 7/8] chore: fix markdown style --- CLAUDE.Rmd | 2 +- CLAUDE.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CLAUDE.Rmd b/CLAUDE.Rmd index aa67569a..5708f075 100644 --- a/CLAUDE.Rmd +++ b/CLAUDE.Rmd @@ -1,7 +1,7 @@ --- -title: "teal.code R Package Development Guide" output: github_document --- +# teal.code R Package Development Guide ```{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE) diff --git a/CLAUDE.md b/CLAUDE.md index 42bc6b7f..7d4a3cc5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,5 @@ -teal.code R Package Development Guide -================ + +# teal.code R Package Development Guide ## Package Overview From 77f24d2d6e5103ecac855fe678e774f8eaa799a6 Mon Sep 17 00:00:00 2001 From: osenan Date: Fri, 25 Sep 2026 15:57:05 +0200 Subject: [PATCH 8/8] chore: modify intro sentence to avoid warnings in the template --- CLAUDE.Rmd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CLAUDE.Rmd b/CLAUDE.Rmd index 5708f075..1a084013 100644 --- a/CLAUDE.Rmd +++ b/CLAUDE.Rmd @@ -38,6 +38,8 @@ For an introduction to `qenv` and its related functions and methods, see @vignet exists. If the expected behavior is unclear, request that an issue be created before making changes that could have unexpected consequences. +This package is part of the teal framework. The following configuration applies to all packages within the teal framework: + ```{r teal_content, echo=FALSE, results='asis'} # Append common shared instructions from teal framework common_claude_url <- "https://raw.githubusercontent.com/insightsengineering/teal/refs/heads/add_common_claude%40main/SHARED_CLAUDE.md" # nolint: line_length_linter.