Browse Source

Merge branch 'main' into CLDC-4230-prefers-not-to-say-consistency-2

CLDC-4230-prefers-not-to-say-consistency-2
Nat Dean-Lewis 3 days ago committed by GitHub
parent
commit
49023f8e6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 79
      .claude/settings.json
  2. 11
      .github/workflows/production_pipeline.yml
  3. 9
      .github/workflows/staging_pipeline.yml
  4. 55
      .github/workflows/upload-sbom.yml
  5. 105
      CLAUDE.md
  6. 6
      Gemfile
  7. 45
      Gemfile.lock
  8. 1
      app/controllers/form_controller.rb
  9. 1
      app/controllers/lettings_logs_controller.rb
  10. 4
      app/controllers/organisations_controller.rb
  11. 1
      app/controllers/sales_logs_controller.rb
  12. 1
      app/controllers/schemes_controller.rb
  13. 1
      app/controllers/users_controller.rb
  14. 15
      app/helpers/application_helper.rb
  15. 2
      app/helpers/user_helper.rb
  16. 25
      app/models/download_record.rb
  17. 2
      app/models/form.rb
  18. 6
      app/models/user.rb
  19. 1
      app/policies/user_policy.rb
  20. 9
      app/services/bulk_upload/sales/year2025/row_parser.rb
  21. 9
      app/services/bulk_upload/sales/year2026/row_parser.rb
  22. 1
      app/services/exports/user_export_constants.rb
  23. 3
      app/services/exports/user_export_service.rb
  24. 78
      app/views/layouts/_footer.html.erb
  25. 16
      app/views/layouts/application.html.erb
  26. 15
      app/views/users/show.html.erb
  27. 4
      config/locales/devise.en.yml
  28. 2
      config/locales/validations/lettings/2025/bulk_upload.en.yml
  29. 2
      config/locales/validations/lettings/2026/bulk_upload.en.yml
  30. 2
      config/locales/validations/sales/2025/bulk_upload.en.yml
  31. 2
      config/locales/validations/sales/2026/bulk_upload.en.yml
  32. 2
      config/sidekiq_cron_schedule.yml
  33. 13
      db/migrate/20260618101455_create_download_record.rb
  34. 5
      db/migrate/20260713105230_allow_null_role_in_download_records.rb
  35. 22
      db/schema.rb
  36. 14
      docs/Gemfile.lock
  37. 2
      package.json
  38. 3
      spec/fixtures/exports/user.xml
  39. 4
      spec/services/bulk_upload/lettings/validator_spec.rb
  40. 436
      yarn.lock

79
.claude/settings.json

@ -0,0 +1,79 @@
{
"enabledPlugins": {
"dependency-track@skillwire": true
},
"permissions": {
"allow": [
"WebSearch",
"Bash(rg:*)",
"Bash(ls:*)",
"Bash(bundle exec rspec:*)",
"Bash(bundle exec rake lint)",
"Bash(bundle exec rake parallel:spec)",
"Bash(bundle exec rake parallel:setup)",
"Bash(bundle exec rubocop:*)",
"Bash(bundle exec erb_lint:*)",
"Bash(bundle exec rails:*)",
"Bash(bundle exec rake db:migrate)",
"Bash(bundle exec rake db:migrate:status)",
"Bash(bundle install)",
"Bash(yarn install)",
"Bash(yarn standard:*)",
"Bash(yarn prettier:*)",
"Bash(yarn stylelint:*)",
"Bash(yarn build:*)",
"Bash(git fetch)",
"Bash(git pull)",
"Bash(git status)",
"Bash(git diff:*)",
"Bash(git log:*)",
"Bash(git show:*)",
"Bash(git branch:*)",
"Bash(git checkout:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Edit(app/**)",
"Edit(config/**)",
"Edit(db/migrate/**)",
"Edit(lib/**)",
"Edit(spec/**)",
"Edit(docs/**)"
],
"deny": [
"Read(.env*)",
"Read(config/master.key)",
"Edit(.env*)",
"Edit(config/master.key)",
"Edit(config/credentials.yml.enc)",
"Edit(db/schema.rb)",
"Edit(db/structure.sql)",
"Edit(Gemfile.lock)",
"Edit(yarn.lock)",
"Edit(AGENTS.md)",
"Edit(AI_CONTEXT.md)",
"Edit(WORKFLOW_AI.md)",
"Bash(curl:*)",
"Bash(wget:*)",
"Bash(sudo:*)",
"Bash(rm -rf:*)",
"Bash(chmod:*)",
"Bash(chown:*)",
"Bash(echo:*)",
"Bash(cat:*)",
"Bash(tail:*)",
"Bash(head:*)",
"Bash(gh:*)",
"Bash(git add .)",
"Bash(git add --all)",
"Bash(git add -A)",
"Bash(git push --force)",
"Bash(git push --force-with-lease)",
"Bash(git reset --hard)",
"Bash(bundle exec rake db:drop)",
"Bash(bundle exec rake db:reset)"
]
},
"env": {
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
}
}

11
.github/workflows/production_pipeline.yml

@ -20,3 +20,14 @@ jobs:
release_tag: ${{ needs.test.outputs.releasetag }}
permissions:
id-token: write
sbom:
name: Upload SBOM
needs: [aws_deploy]
permissions:
contents: read
uses: ./.github/workflows/upload-sbom.yml
with:
projectversion: prod
secrets:
DTRACK_API_KEY: ${{ secrets.DTRACK_API_KEY }}

9
.github/workflows/staging_pipeline.yml

@ -32,6 +32,15 @@ jobs:
permissions:
id-token: write
sbom:
name: Upload SBOM
needs: [aws_deploy]
uses: ./.github/workflows/upload-sbom.yml
with:
projectversion: staging
secrets:
DTRACK_API_KEY: ${{ secrets.DTRACK_API_KEY }}
performance:
needs: [aws_deploy]
runs-on: ubuntu-latest

55
.github/workflows/upload-sbom.yml

@ -0,0 +1,55 @@
name: Upload SBOM
# Generates a CycloneDX SBOM with Syft (auto-detects both the Ruby gems in
# Gemfile.lock and the Node packages in yarn.lock) and uploads it to
# Dependency-Track. Called from the staging and production pipelines after a
# successful deploy.
on:
workflow_call:
inputs:
projectversion:
required: true
type: string
secrets:
DTRACK_API_KEY:
required: true
permissions:
contents: read
jobs:
sbom:
name: Generate and upload SBOM
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
echo "SYFT_VERSION=v1.45.0" >> "$GITHUB_ENV"
echo "SYFT_SCRIPT_SHA=9673f867e50398b5d25ec97ff051a451c46d262c" >> "$GITHUB_ENV"
- uses: actions/cache@v4
with:
path: /usr/local/bin/syft
key: syft-${{ env.SYFT_VERSION }}
- name: Install Syft
run: |
[ -f /usr/local/bin/syft ] || \
curl -sSfL "https://raw.githubusercontent.com/anchore/syft/${SYFT_SCRIPT_SHA}/install.sh" | sh -s -- -b /usr/local/bin "$SYFT_VERSION"
- run: syft . -o cyclonedx-xml=bom.xml
- name: Upload SBOM to Dependency-Track
env:
DTRACK_API_KEY: ${{ secrets.DTRACK_API_KEY }}
SBOM_VERSION: ${{ inputs.projectversion }}
run: |
curl -sSf -X POST "https://api-deps.softwire.com/api/v1/bom" \
-H "X-Api-Key: $DTRACK_API_KEY" \
-F "autoCreate=true" \
-F "projectName=CORE" \
-F "projectVersion=$SBOM_VERSION" \
-F "parentName=Support" \
-F "bom=@bom.xml"

105
CLAUDE.md

@ -0,0 +1,105 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project
CORE — a Ruby on Rails app (the "Submit social housing lettings and sales data" service) that collects lettings and sales of social housing data in England for MHCLG. Data providers (Local Authorities and Private Registered Providers) submit logs; the data is exported nightly to CDS (Consolidated Data Store) via XML to S3.
Stack: Ruby 3.4.9, Rails 7.2, PostgreSQL, Sidekiq + Redis, Webpack/Propshaft, Stimulus, ViewComponent, GOV.UK Design System (govuk-frontend, govuk-components, govuk_design_system_formbuilder), Devise + 2FA, Pundit, PaperTrail.
Full domain/architecture docs live in `docs/` (rendered at https://communitiesuk.github.io/submit-social-housing-lettings-and-sales-data) and are the authoritative reference — especially `docs/index.md` (domain overview), `docs/form/*` (form architecture), `docs/bulk_upload.md`, `docs/exports.md`, `docs/csv_downloads.md`, and the ADRs in `docs/adr/`.
## Commands
Run app (Rails + Sidekiq's redis + JS watch via Foreman):
```bash
./bin/dev
```
Rails server alone: `bundle exec rails s` (port 3000). JS watch alone: `yarn build --mode=development --watch`. First-time asset build: `yarn build --mode=development`.
Tests:
```bash
bundle exec rspec # full suite
bundle exec rspec ./spec/path/to/file.rb # single file
bundle exec rspec ./spec/path/to/file.rb:42 # single example by line
bundle exec rake parallel:setup # one-time setup
RAILS_ENV=test bundle exec rake parallel:spec # parallel run
```
If you change the schema, run `bundle exec rake db:migrate RAILS_ENV=test` before running specs.
Lint (everything): `bundle exec rake lint`. Individual linters:
```bash
bundle exec rubocop # -a safe autocorrect, -A all
bundle exec erb_lint --lint-all
yarn standard # --fix to autocorrect
yarn stylelint app/frontend/styles
yarn prettier . --check # --write to autocorrect
```
Database: `bundle exec rake db:create db:migrate db:seed`. Seeded users use the password from `REVIEW_APP_USER_PASSWORD` in `.env` (default `password`).
## Architecture
### The form system (core abstraction)
Form data collection runs on annual windows (1 April → 1 April + 3-month late-submission tail). Two forms may be active simultaneously during the April–June/July **crossover period**.
`FormHandler` (singleton, `app/models/form_handler.rb`) holds every active form: `current/previous/next/archived` × `lettings/sales`. Each `Form` is built from Ruby classes (not JSON — historical JSON definitions in `config/forms/` are legacy; new forms are defined in code under `app/models/form/lettings/{sections,subsections,pages,questions}` and `app/models/form/sales/...`).
Hierarchy: `Form``Section``Subsection``Page``Question`. Pages route via `depends_on` conditions (with chained method calls e.g. `{ "owning_organisation.provider_type": "local_authority" }`) or custom `routed_to?` methods. Questions can be `conditional_for` (inline conditional on the same page), `derived` (computed, not cleared when unrouted), or `inferred` (cleared when their source changes). See `docs/form/builder.md` for the full DSL.
Key consequence: **every question id must match an ActiveRecord column on `LettingsLog` / `SalesLog`**. Checkbox questions need one column per answer option. Adding a question is a migration + form class change in lock-step.
`Form::DEADLINES` in `app/models/form.rb` is the source of truth for collection year cutoffs (new_logs_end_date, submission_deadline, edit_end_date). Add a year here when introducing a new collection window.
### Logs
`LettingsLog` and `SalesLog` (both inherit shared behaviour from `Log`) are the primary records. Sales splits into discounted ownership, shared-ownership initial, and staircasing (post-2024); pre-2025 also included outright sales. Lettings splits into general needs and supported housing (which belongs to a `Scheme` with one or more `Location`s).
Validations live in `app/models/validations/` (lettings) and `app/models/validations/sales/` and are mixed into the log models. Soft validations show interruption pages instead of hard errors.
### Organisations & permissions
Three user roles outside of MHCLG: **data providers**, **data coordinators** (org admins, can also complete logs), plus an optional **data protection officer (DPO)** flag on a user. Internal roles: **support** (full admin) and **statisticians**. Orgs form parent/child stock-owning/managing relationships (many-to-many), and a user's access to a log depends on whether their org owns or manages it. Pundit policies in `app/policies/` enforce this — always check the policy when adding controller actions.
### Bulk upload
Users upload a CSV per log-type per year; the file is saved to S3 and `ProcessBulkUploadJob` runs `BulkUpload::Processor`, which picks year- and type-specific `CsvParser`, `RowParser`, `Validator`, and `LogCreator` classes from `app/services/bulk_upload/`. Outcomes: clean upload, partial upload (requires user approval after error email), or rejected (template/critical errors). See `docs/bulk_upload.md`.
### Exports to CDS
`Exports::ExportService` orchestrates a nightly Sidekiq cron job that writes XML + manifests to S3 for ingestion by the Consolidated Data Store. Year-specific collections (lettings logs) can produce up to three concurrent collections during crossover. Field-level mapping lives in `lettings_log_export_service.rb`, `organisation_export_service.rb`, `user_export_service.rb`, gated by `EXPORT_FIELDS` constants in the matching `*_export_constants.rb` files (with `POST_<YEAR>_EXPORT_FIELDS` for year-gated additions). Partial vs full export semantics are documented in `docs/exports.md`.
### CSV downloads
User-facing CSV downloads are also async via Sidekiq, delivered as S3 presigned URLs by email. Logs CSVs come in **labels** (human-readable) and **codes** (numeric, aligned with bulk upload / CDS) variants. Column selection lives in `lettings_log_attributes` / `sales_log_attributes` / `scheme_attributes` / `location_attributes` and (for users) `User.download_attributes`. Header descriptions live in the `csv_variable_definitions` table and are edited via `/admin`.
### Rake tasks
`lib/tasks/` holds many one-off and operational rake tasks (data corrections, migrations, exports, form-definition dumps). For ad-hoc production runs, tasks are executed as ECS Fargate tasks against `core-$env-ad-hoc` — see `docs/rake.md`.
### Feature toggles
`app/services/feature_toggle.rb` — simple class methods, no external flag service.
### Frontend conventions
- Service-specific components live in `app/components/` (ViewComponent), with `app-*` BEM class names to avoid clashing with the `govuk-*` Design System.
- Stimulus controllers: register in `app/frontend/controllers/index.js` (kebab-case) and define in `app/frontend/controllers/` (underscore_case).
- Webpack bundles JS/CSS via `jsbundling-rails` + `cssbundling-rails`; Propshaft serves the bundled assets. Babel transpiles to ES5 for IE compatibility (polyfills in `app/frontend/application.js`).
## Testing notes
- Prefer request specs over feature specs (faster, still cover route + controller + model + view). Use feature specs only when JavaScript or interaction-specific assertions are needed.
- FactoryBot factories have deep callback chains: creating a `lettings_log`/`sales_log` also creates a `User`, `Organisation`, often a `DataProtectionConfirmation`, and an `OrganisationRentPeriod` (lettings only, if `period` is set). See `docs/testing.md` for the full breakdown — be aware that tests appearing to "just create a log" may be persisting several other records.
- Capybara runs headless by default and uses Gecko driver for `js: true` specs (toggle in `spec/rails_helper.rb`).
## Commit & PR conventions
Commits and PRs are prefixed with the Jira ticket id (e.g. `CLDC-4300: ...`) — see `git log` for examples. Pre-commit hooks (Overcommit) run RuboCop and schema-up-to-date checks; do not skip them.

6
Gemfile

@ -119,7 +119,7 @@ end
gem "cssbundling-rails"
gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby]
gem "excon", "~> 0.111.0"
gem "excon", ">= 1.5.0"
# faraday is a transitive dependency, but we need to force the version to 2.14.1 to avoid a vulnerability.
gem "faraday", ">= 2.14.1"
# faraday is a transitive dependency, but we need to force the version to 2.14.3 to avoid a vulnerability.
gem "faraday", ">= 2.14.3"

45
Gemfile.lock

@ -149,17 +149,17 @@ GEM
capybara-screenshot (1.0.26)
capybara (>= 1.0, < 4)
launchy
cgi (0.5.1)
cgi (0.5.2)
childprocess (5.0.0)
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
concurrent-ruby (1.3.6)
concurrent-ruby (1.3.7)
connection_pool (2.5.5)
crack (1.0.0)
bigdecimal
rexml
crass (1.0.6)
crass (1.0.7)
cronex (0.15.0)
tzinfo
unicode (>= 0.4.4.5)
@ -202,7 +202,8 @@ GEM
et-orbi (1.4.0)
tzinfo
event_stream_parser (1.0.0)
excon (0.111.0)
excon (1.5.0)
logger
factory_bot (6.5.0)
activesupport (>= 5.0.0)
factory_bot_rails (6.4.4)
@ -210,7 +211,7 @@ GEM
railties (>= 5.0.0)
faker (3.2.3)
i18n (>= 1.8.11, < 2)
faraday (2.14.2)
faraday (2.14.3)
faraday-net_http (>= 2.0, < 3.5)
json
logger
@ -224,10 +225,10 @@ GEM
raabro (~> 1.4)
globalid (1.3.0)
activesupport (>= 6.1)
govuk-components (6.2.0)
govuk-components (6.4.0)
html-attributes-utils (~> 1.0.0, >= 1.0.0)
pagy (>= 6, < 10)
view_component (>= 4.9, < 4.10)
view_component (>= 4.9, < 4.13)
govuk_design_system_formbuilder (5.7.1)
actionview (>= 6.1)
activemodel (>= 6.1)
@ -239,7 +240,7 @@ GEM
hashdiff (1.1.0)
html-attributes-utils (1.0.2)
activesupport (>= 6.1.4.4)
i18n (1.14.8)
i18n (1.15.2)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
iniparse (1.5.0)
@ -252,7 +253,7 @@ GEM
jmespath (1.6.2)
jsbundling-rails (1.3.0)
railties (>= 6.0.0)
json (2.19.8)
json (2.20.0)
json-schema (4.1.1)
addressable (>= 2.8)
jwt (3.2.0)
@ -277,7 +278,7 @@ GEM
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.7.0)
loofah (2.25.1)
loofah (2.25.2)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
@ -290,12 +291,12 @@ GEM
method_source (1.1.0)
mini_mime (1.1.5)
minitest (5.27.0)
msgpack (1.7.2)
msgpack (1.8.3)
multipart-post (2.4.1)
nested_form (0.3.2)
net-http (0.9.1)
uri (>= 0.11.1)
net-imap (0.6.4)
net-imap (0.6.4.1)
date
net-protocol
net-pop (0.1.2)
@ -305,13 +306,13 @@ GEM
net-smtp (0.5.1)
net-protocol
nio4r (2.7.5)
nokogiri (1.19.3-arm64-darwin)
nokogiri (1.19.4-arm64-darwin)
racc (~> 1.4)
nokogiri (1.19.3-x86_64-darwin)
nokogiri (1.19.4-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.19.3-x86_64-linux-gnu)
nokogiri (1.19.4-x86_64-linux-gnu)
racc (~> 1.4)
nokogiri (1.19.3-x86_64-linux-musl)
nokogiri (1.19.4-x86_64-linux-musl)
racc (~> 1.4)
notifications-ruby-client (6.4.0)
jwt (>= 1.5, < 4)
@ -390,8 +391,8 @@ GEM
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.7.0)
loofah (~> 2.25)
rails-html-sanitizer (1.7.1)
loofah (~> 2.25, >= 2.25.2)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rails_admin (3.3.0)
activemodel-serializers-xml (>= 1.0)
@ -550,7 +551,7 @@ GEM
activerecord (>= 6.1)
uri (1.1.1)
useragent (0.16.11)
view_component (4.9.0)
view_component (4.12.0)
actionview (>= 7.1.0)
activesupport (>= 7.1.0)
concurrent-ruby (~> 1)
@ -570,7 +571,7 @@ GEM
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
websocket (1.2.10)
websocket-driver (0.7.7)
websocket-driver (0.8.2)
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
@ -600,10 +601,10 @@ DEPENDENCIES
devise_two_factor_authentication
dotenv-rails
erb_lint
excon (~> 0.111.0)
excon (>= 1.5.0)
factory_bot_rails
faker
faraday (>= 2.14.1)
faraday (>= 2.14.3)
govuk-components (~> 6.2)
govuk_design_system_formbuilder (~> 5.7)
govuk_markdown

1
app/controllers/form_controller.rb

@ -25,6 +25,7 @@ class FormController < ApplicationController
updated_question = @page.questions.reject { |question| question.check_answer_label.blank? }.first
updated_question_string = [updated_question&.question_number_string, updated_question&.check_answer_label.to_s.downcase].compact.join(": ")
flash[:notice] = "You have successfully updated #{updated_question_string}"
flash[:notification_banner_two_thirds] = true
end
update_duplication_tracking

1
app/controllers/lettings_logs_controller.rb

@ -96,6 +96,7 @@ class LettingsLogsController < LogsController
end
def email_csv
DownloadRecord.build_from_user(download_type: :lettings_log, download_filters: session_filters.to_s, user: current_user).save!
all_orgs = params["organisation_select"] == "all"
EmailCsvJob.perform_later(current_user, search_term, session_filters, all_orgs, nil, codes_only_export?, "lettings", session_filters["years"].first.to_i)
redirect_to csv_confirmation_lettings_logs_path

4
app/controllers/organisations_controller.rb

@ -40,6 +40,7 @@ class OrganisationsController < ApplicationController
end
def email_schemes_csv
DownloadRecord.build_from_user(download_type: :scheme_location, download_filters: session_filters.merge({ download_type: params[:download_type] }).to_s, user: current_user).save!
SchemeEmailCsvJob.perform_later(current_user, search_term, session_filters, false, @organisation, params[:download_type])
redirect_to schemes_csv_confirmation_organisation_path
end
@ -72,6 +73,7 @@ class OrganisationsController < ApplicationController
end
end
format.csv do
DownloadRecord.build_from_user(download_type: :user, download_filters: session_filters.to_s, user: current_user).save!
send_data byte_order_mark + unpaginated_filtered_users.to_csv, filename: "users-#{@organisation.name}-#{Time.zone.now}.csv"
end
end
@ -205,6 +207,7 @@ class OrganisationsController < ApplicationController
end
def email_lettings_csv
DownloadRecord.build_from_user(download_type: :lettings_log, download_filters: session_filters.to_s, user: current_user).save!
EmailCsvJob.perform_later(current_user, search_term, session_filters, false, @organisation, codes_only_export?, "lettings", session_filters["years"].first.to_i)
redirect_to lettings_logs_csv_confirmation_organisation_path
end
@ -243,6 +246,7 @@ class OrganisationsController < ApplicationController
end
def email_sales_csv
DownloadRecord.build_from_user(download_type: :sales_log, download_filters: session_filters.to_s, user: current_user).save!
EmailCsvJob.perform_later(current_user, search_term, session_filters, false, @organisation, codes_only_export?, "sales", session_filters["years"].first.to_i)
redirect_to sales_logs_csv_confirmation_organisation_path
end

1
app/controllers/sales_logs_controller.rb

@ -70,6 +70,7 @@ class SalesLogsController < LogsController
end
def email_csv
DownloadRecord.build_from_user(download_type: :sales_log, download_filters: session_filters.to_s, user: current_user).save!
all_orgs = params["organisation_select"] == "all"
EmailCsvJob.perform_later(current_user, search_term, session_filters, all_orgs, nil, codes_only_export?, "sales", session_filters["years"].first.to_i)
redirect_to csv_confirmation_sales_logs_path

1
app/controllers/schemes_controller.rb

@ -236,6 +236,7 @@ class SchemesController < ApplicationController
end
def email_csv
DownloadRecord.build_from_user(download_type: :scheme_location, download_filters: session_filters.merge({ download_type: params[:download_type] }).to_s, user: current_user).save!
all_orgs = params["organisation_select"] == "all"
SchemeEmailCsvJob.perform_later(current_user, search_term, session_filters, all_orgs, nil, params[:download_type])
redirect_to csv_confirmation_schemes_path

1
app/controllers/users_controller.rb

@ -24,6 +24,7 @@ class UsersController < ApplicationController
format.html
format.csv do
if current_user.support?
DownloadRecord.build_from_user(download_type: :user, download_filters: session_filters.to_s, user: current_user).save!
send_data byte_order_mark + filtered_users.to_csv, filename: "users-#{Time.zone.now}.csv"
else
head :unauthorized

15
app/helpers/application_helper.rb

@ -29,6 +29,21 @@ module ApplicationHelper
!request.path.match?(/\/notifications\/\d+$/) && (authenticated_user_has_notifications? || unauthenticated_user_has_notifications?)
end
def notification_banner
govuk_notification_banner(
title_text: "Success",
success: true,
title_heading_level: 3,
title_id: "flash-notice",
role: "alert",
) do |notification_banner|
notification_banner.with_heading(text: flash.notice.html_safe)
if flash[:notification_banner_body]
tag.p flash[:notification_banner_body]&.html_safe
end
end
end
private
def paginated_title(title, pagy)

2
app/helpers/user_helper.rb

@ -91,7 +91,7 @@ module UserHelper
end
def user_action_text(user, attribute)
return "Change" if %w[role phone].include?(attribute) && user.send(attribute).present?
return "Change" if attribute == "phone_extension" || (%w[role phone].include?(attribute) && user.send(attribute).present?)
""
end

25
app/models/download_record.rb

@ -0,0 +1,25 @@
# Used to allow for easier auditing of what users downloaded what info
# Caches some info about the user at the time of download
class DownloadRecord < ApplicationRecord
belongs_to :user
belongs_to :user_organisation, class_name: "Organisation"
DOWNLOAD_TYPE = {
user: 0,
lettings_log: 1,
sales_log: 2,
scheme_location: 3,
}.freeze
enum download_type: DOWNLOAD_TYPE
enum user_role: User::ROLES
def self.build_from_user(user:, **attrs)
new(
user:,
user_organisation: user.organisation,
user_role: user.role,
**attrs,
)
end
end

2
app/models/form.rb

@ -21,7 +21,7 @@ class Form
},
2025 => {
submission_deadline: Time.zone.local(2026, 6, 5),
new_logs_end_date: Time.zone.local(2026, 7, 3),
new_logs_end_date: Time.zone.local(2026, 7, 12),
edit_end_date: Time.zone.local(2026, 8, 1),
},
2026 => {

6
app/models/user.rb

@ -335,12 +335,6 @@ class User < ApplicationRecord
save!(validate: false)
end
def phone_with_extension
return phone if phone_extension.blank?
"#{phone}, Ext. #{phone_extension}"
end
def assigned_to_lettings_logs
lettings_logs.where(assigned_to: self)
end

1
app/policies/user_policy.rb

@ -26,6 +26,7 @@ class UserPolicy
%w[
edit_emails?
edit_telephone_numbers?
edit_extension_numbers?
edit_names?
].each do |method_name|
define_method method_name do

9
app/services/bulk_upload/sales/year2025/row_parser.rb

@ -781,7 +781,7 @@ private
postcode_full: %i[field_21 field_22],
wchair: %i[field_27],
type: %i[field_9 field_11 field_8],
type: sale_type_fields,
resale: %i[field_78],
hodate: %i[field_80 field_81 field_82],
@ -1209,6 +1209,13 @@ private
field_109 if staircasing?
end
def sale_type_fields
return %i[field_8 field_9] if shared_ownership?
return %i[field_8 field_11] if discounted_ownership?
%i[field_9 field_11 field_8]
end
def value_fields
return [:field_86] if shared_ownership_initial_purchase?
return [:field_113] if discounted_ownership?

9
app/services/bulk_upload/sales/year2026/row_parser.rb

@ -836,7 +836,7 @@ private
postcode_full: %i[field_21 field_22],
wchair: %i[field_28],
type: %i[field_9 field_11 field_8],
type: sale_type_fields,
resale: %i[field_91],
hodate: %i[field_93 field_94 field_95],
@ -1342,6 +1342,13 @@ private
field_122 if staircasing?
end
def sale_type_fields
return %i[field_8 field_9] if shared_ownership?
return %i[field_8 field_11] if discounted_ownership?
%i[field_9 field_11 field_8]
end
def value_fields
return [:field_99] if shared_ownership_initial_purchase?
return [:field_128] if discounted_ownership?

1
app/services/exports/user_export_constants.rb

@ -6,6 +6,7 @@ module Exports::UserExportConstants
"email",
"name",
"phone",
"extension_number",
"organisation_id",
"organisation_name",
"role",

3
app/services/exports/user_export_service.rb

@ -64,7 +64,8 @@ module Exports
attribute_hash["role"] = user.role
attribute_hash["organisation_name"] = user.organisation.name
attribute_hash["active"] = user.active?
attribute_hash["phone"] = [user.phone, user.phone_extension].compact.join(" ")
attribute_hash["phone"] = user.phone
attribute_hash["extension_number"] = user.phone_extension
attribute_hash["last_sign_in_at"] = user.last_sign_in_at&.iso8601
attribute_hash
end

78
app/views/layouts/_footer.html.erb

@ -1,42 +1,44 @@
<%= govuk_footer do |footer| %>
<%= footer.with_meta do %>
<div class="govuk-footer__meta-item govuk-footer__meta-item--grow">
<h2 class="govuk-heading-m">Get help with this service</h2>
<div class="govuk-grid-row">
<div class="govuk-grid-column-one-third-from-desktop">
<h3 class="govuk-heading-s govuk-!-margin-bottom-1">Online helpdesk</h3>
<p class="govuk-body govuk-!-font-size-16">
<%= govuk_link_to("CORE helpdesk (opens in a new tab)", GlobalConstants::HELPDESK_URL, class: "govuk-footer__link", rel: "noreferrer noopener", target: "_blank") %>
</p>
</div>
<footer class="govuk-footer" role="contentinfo">
<div class="govuk-width-container">
<div class="govuk-footer__meta">
<div class="govuk-footer__meta-item govuk-footer__meta-item--grow">
<h2 class="govuk-heading-m">Get help with this service</h2>
<div class="govuk-grid-row">
<div class="govuk-grid-column-one-third-from-desktop">
<h3 class="govuk-heading-s govuk-!-margin-bottom-1">Online helpdesk</h3>
<p class="govuk-body govuk-!-font-size-16">
<%= govuk_link_to("CORE helpdesk (opens in a new tab)", GlobalConstants::HELPDESK_URL, class: "govuk-footer__link", rel: "noreferrer noopener", target: "_blank") %>
</p>
</div>
<div class="govuk-grid-column-one-third-from-desktop">
<h3 class="govuk-heading-s govuk-!-margin-bottom-1">Telephone</h3>
<ul class="govuk-list govuk-!-font-size-16">
<li>0333 202 5084</li>
<li>Monday to Friday, 9am to 5:30pm<br>(except public holidays)</li>
</ul>
<div class="govuk-grid-column-one-third-from-desktop">
<h3 class="govuk-heading-s govuk-!-margin-bottom-1">Telephone</h3>
<ul class="govuk-list govuk-!-font-size-16">
<li>0333 202 5084</li>
<li>Monday to Friday, 9am to 5:30pm<br>(except public holidays)</li>
</ul>
</div>
</div>
</div>
<h2 class="govuk-visually-hidden">Helpful links</h2>
<ul class="govuk-footer__inline-list govuk-!-margin-top-6 govuk-!-margin-bottom-0">
<li class="govuk-footer__inline-list-item">
<%= govuk_link_to("Accessibility statement", accessibility_statement_path, class: "govuk-footer__link") %>
</li>
<li class="govuk-footer__inline-list-item">
<%= govuk_link_to("Privacy notice", privacy_notice_path, class: "govuk-footer__link") %>
</li>
<li class="govuk-footer__inline-list-item">
<%= govuk_link_to("View source code", "https://github.com/communitiesuk/submit-social-housing-lettings-and-sales-data", class: "govuk-footer__link") %>
</li>
<li class="govuk-footer__inline-list-item">
<%= govuk_link_to("Cookies", cookies_path, class: "govuk-footer__link") %>
</li>
</ul>
</div>
<div class="govuk-footer__meta-item">
<a class="govuk-footer__link govuk-footer__copyright-logo govuk-!-margin-bottom-1" href="https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/">© Crown copyright</a>
<h2 class="govuk-visually-hidden">Helpful links</h2>
<ul class="govuk-footer__inline-list govuk-!-margin-top-6 govuk-!-margin-bottom-0">
<li class="govuk-footer__inline-list-item">
<%= govuk_link_to("Accessibility statement", accessibility_statement_path, class: "govuk-footer__link") %>
</li>
<li class="govuk-footer__inline-list-item">
<%= govuk_link_to("Privacy notice", privacy_notice_path, class: "govuk-footer__link") %>
</li>
<li class="govuk-footer__inline-list-item">
<%= govuk_link_to("View source code", "https://github.com/communitiesuk/submit-social-housing-lettings-and-sales-data", class: "govuk-footer__link") %>
</li>
<li class="govuk-footer__inline-list-item">
<%= govuk_link_to("Cookies", cookies_path, class: "govuk-footer__link") %>
</li>
</ul>
</div>
<div class="govuk-footer__meta-item">
<a class="govuk-footer__link govuk-footer__copyright-logo govuk-!-margin-bottom-1" href="https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/">© Crown copyright</a>
</div>
</div>
<% end %>
<% end %>
</div>
</footer>

16
app/views/layouts/application.html.erb

@ -124,17 +124,11 @@
<main class="govuk-main-wrapper govuk-main-wrapper--auto-spacing" id="main-content" role="main">
<% if flash.notice && !flash.notice.include?("translation missing") %>
<%= govuk_notification_banner(
title_text: "Success",
success: true, title_heading_level: 3,
title_id: "flash-notice",
role: "alert"
) do |notification_banner|
notification_banner.with_heading(text: flash.notice.html_safe)
if flash[:notification_banner_body]
tag.p flash[:notification_banner_body]&.html_safe
end
end %>
<div class="govuk-grid-row">
<div class="<%= flash[:notification_banner_two_thirds] ? "govuk-grid-column-two-thirds-from-desktop" : "govuk-grid-column-full" %>">
<%= notification_banner %>
</div>
</div>
<% end %>
<%= content_for?(:content) ? yield(:content) : yield %>
</main>

15
app/views/users/show.html.erb

@ -65,6 +65,21 @@
end
end %>
<%= summary_list.with_row do |row|
row.with_key { "Extension number" }
row.with_value { user_details_html(@user, current_user, "phone_extension") }
if UserPolicy.new(current_user, @user).edit_extension_numbers?
row.with_action(
text: user_action_text(@user, "phone_extension"),
visually_hidden_text: "extension number",
href: aliased_user_edit(@user, current_user),
html_attributes: { "data-qa": "change-extension-number" },
)
else
row.with_action
end
end %>
<%= summary_list.with_row do |row|
row.with_key { "Password" }
row.with_value { "••••••••" }

4
config/locales/devise.en.yml

@ -10,8 +10,8 @@ en:
already_authenticated: "You are already signed in."
inactive: "Your account has not been activated yet."
invalid: "Incorrect %{authentication_keys} or password."
locked: "Your account has been locked."
last_attempt: "You have one more attempt before your account is locked."
locked: "Your account has been locked. Please wait one hour to try again."
last_attempt: "You have one more attempt before your account is locked for one hour. You may prefer to click \"Forgot password\" to reset your password."
not_found_in_database: "Incorrect %{authentication_keys} or password."
timeout: "Your session expired. Sign in again to continue."
unauthenticated: "You need to sign in or sign up before continuing."

2
config/locales/validations/lettings/2025/bulk_upload.en.yml

@ -12,7 +12,7 @@ en:
wrong_template:
wrong_template: "Incorrect start dates, please ensure you have used the correct template."
no_headers: "Your file does not contain the required header rows. Add or check the header rows and upload your file again. [Read more about using the template headers](%{guidance_link})."
wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the correct template."
wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the template for lettings 2025."
over_max_column_count: "Too many columns, please ensure you have used the correct template."
owning_organisation:
not_found: "The owning organisation code is incorrect."

2
config/locales/validations/lettings/2026/bulk_upload.en.yml

@ -12,7 +12,7 @@ en:
wrong_template:
wrong_template: "Incorrect start dates, please ensure you have used the correct template."
no_headers: "Your file does not contain the required header rows. Add or check the header rows and upload your file again. [Read more about using the template headers](%{guidance_link})."
wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the correct template."
wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the template for lettings 2026."
over_max_column_count: "Too many columns, please ensure you have used the correct template."
owning_organisation:
not_found: "The owning organisation code is incorrect."

2
config/locales/validations/sales/2025/bulk_upload.en.yml

@ -11,7 +11,7 @@ en:
wrong_template:
over_max_column_count: "Too many columns, please ensure you have used the correct template."
no_headers: "Your file does not contain the required header rows. Add or check the header rows and upload your file again. [Read more about using the template headers](%{guidance_link})."
wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the correct template."
wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the template for sales 2025."
wrong_template: "Incorrect sale dates, please ensure you have used the correct template."
numeric:
within_range: "%{field} must be between %{min} and %{max}."

2
config/locales/validations/sales/2026/bulk_upload.en.yml

@ -11,7 +11,7 @@ en:
wrong_template:
over_max_column_count: "Too many columns, please ensure you have used the correct template."
no_headers: "Your file does not contain the required header rows. Add or check the header rows and upload your file again. [Read more about using the template headers](%{guidance_link})."
wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the correct template."
wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the template for sales 2026."
wrong_template: "Incorrect sale dates, please ensure you have used the correct template."
numeric:
within_range: "%{field} must be between %{min} and %{max}."

2
config/sidekiq_cron_schedule.yml

@ -1,4 +1,4 @@
data_export_xml:
cron: "every day at 5am"
cron: "every day at midnight"
class: "DataExportXmlJob"
queue: default

13
db/migrate/20260618101455_create_download_record.rb

@ -0,0 +1,13 @@
class CreateDownloadRecord < ActiveRecord::Migration[7.2]
def change
create_table :download_records do |t|
t.integer :download_type, null: false
t.string :download_filters, null: false
t.references :user, null: false, foreign_key: true
t.references :user_organisation, null: false, foreign_key: { to_table: :organisations }
t.integer :user_role, null: false
t.timestamps
end
end
end

5
db/migrate/20260713105230_allow_null_role_in_download_records.rb

@ -0,0 +1,5 @@
class AllowNullRoleInDownloadRecords < ActiveRecord::Migration[7.2]
def change
change_column_null :download_records, :user_role, true
end
end

22
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2026_04_20_151627) do
ActiveRecord::Schema[7.2].define(version: 2026_07_13_105230) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -106,6 +106,18 @@ ActiveRecord::Schema[7.2].define(version: 2026_04_20_151627) do
t.index ["organisation_id"], name: "index_data_protection_confirmations_on_organisation_id"
end
create_table "download_records", force: :cascade do |t|
t.integer "download_type", null: false
t.string "download_filters", null: false
t.bigint "user_id", null: false
t.bigint "user_organisation_id", null: false
t.integer "user_role"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_download_records_on_user_id"
t.index ["user_organisation_id"], name: "index_download_records_on_user_organisation_id"
end
create_table "exports", force: :cascade do |t|
t.datetime "created_at", default: -> { "CURRENT_TIMESTAMP" }
t.datetime "started_at", null: false
@ -825,9 +837,6 @@ ActiveRecord::Schema[7.2].define(version: 2026_04_20_151627) do
t.string "sexrab5"
t.string "sexrab6"
t.integer "buildheightclass"
t.integer "mortlen_known"
t.integer "hasservicechargeschanged"
t.decimal "newservicecharges", precision: 10, scale: 2
t.integer "gender_same_as_sex1"
t.integer "gender_same_as_sex2"
t.integer "gender_same_as_sex3"
@ -840,6 +849,9 @@ ActiveRecord::Schema[7.2].define(version: 2026_04_20_151627) do
t.string "gender_description4"
t.string "gender_description5"
t.string "gender_description6"
t.integer "mortlen_known"
t.integer "hasservicechargeschanged"
t.decimal "newservicecharges", precision: 10, scale: 2
t.index ["assigned_to_id"], name: "index_sales_logs_on_assigned_to_id"
t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id"
t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id"
@ -944,6 +956,8 @@ ActiveRecord::Schema[7.2].define(version: 2026_04_20_151627) do
t.index ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id"
end
add_foreign_key "download_records", "organisations", column: "user_organisation_id"
add_foreign_key "download_records", "users"
add_foreign_key "lettings_logs", "locations"
add_foreign_key "lettings_logs", "organisations", column: "owning_organisation_id", on_delete: :cascade
add_foreign_key "lettings_logs", "schemes"

14
docs/Gemfile.lock

@ -24,7 +24,7 @@ GEM
coffee-script-source (1.11.1)
colorator (1.1.0)
commonmarker (0.23.10)
concurrent-ruby (1.3.6)
concurrent-ruby (1.3.7)
connection_pool (3.0.2)
csv (3.3.5)
dnsruby (1.61.9)
@ -37,11 +37,11 @@ GEM
ffi (>= 1.15.0)
eventmachine (1.2.7)
execjs (2.8.1)
faraday (2.14.1)
faraday (2.14.3)
faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-net_http (3.4.2)
faraday-net_http (3.4.4)
net-http (~> 0.5)
ffi (1.15.5)
forwardable-extended (2.6.0)
@ -211,7 +211,7 @@ GEM
gemoji (~> 3.0)
html-pipeline (~> 2.2)
jekyll (>= 3.0, < 5.0)
json (2.19.2)
json (2.19.9)
kramdown (2.3.2)
rexml
kramdown-parser-gfm (1.1.0)
@ -229,11 +229,11 @@ GEM
minitest (5.27.0)
net-http (0.9.1)
uri (>= 0.11.1)
nokogiri (1.19.1-arm64-darwin)
nokogiri (1.19.4-arm64-darwin)
racc (~> 1.4)
nokogiri (1.19.1-x86_64-darwin)
nokogiri (1.19.4-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.19.1-x86_64-linux-gnu)
nokogiri (1.19.4-x86_64-linux-gnu)
racc (~> 1.4)
octokit (4.25.1)
faraday (>= 1, < 3)

2
package.json

@ -5,7 +5,7 @@
"node": "^24.0.0"
},
"dependencies": {
"@babel/core": "^7.17.7",
"@babel/core": "^7.29.6",
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"@hotwired/stimulus": "^3.0.0",

3
spec/fixtures/exports/user.xml vendored

@ -8,7 +8,8 @@
<sign_in_count>5</sign_in_count>
<last_sign_in_at>2022-03-03T00:00:00+00:00</last_sign_in_at>
<role>data_provider</role>
<phone>1234512345123 123</phone>
<phone>1234512345123</phone>
<extension_number>123</extension_number>
<is_dpo>false</is_dpo>
<is_key_contact>false</is_key_contact>
<active>true</active>

4
spec/services/bulk_upload/lettings/validator_spec.rb

@ -55,7 +55,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
it "is not valid" do
expect(validator).not_to be_valid
expect(validator.errors["base"]).to eql(["Incorrect number of fields, please ensure you have used the correct template."])
expect(validator.errors["base"]).to eql(["Incorrect number of fields, please ensure you have used the template for lettings #{year}."])
end
end
@ -72,7 +72,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
it "is not valid" do
expect(validator).not_to be_valid
expect(validator.errors["base"]).to eql(["Incorrect number of fields, please ensure you have used the correct template."])
expect(validator.errors["base"]).to eql(["Incorrect number of fields, please ensure you have used the template for lettings #{year}."])
end
end
end

436
yarn.lock

@ -2,66 +2,56 @@
# yarn lockfile v1
"@ampproject/remapping@^2.2.0":
version "2.3.0"
resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz"
integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.29.0", "@babel/code-frame@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7"
integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==
dependencies:
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.24"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.24.7":
version "7.24.7"
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz"
integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==
dependencies:
"@babel/highlight" "^7.24.7"
picocolors "^1.0.0"
"@babel/code-frame@^7.26.2":
version "7.26.2"
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz"
integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
dependencies:
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/helper-validator-identifier" "^7.29.7"
js-tokens "^4.0.0"
picocolors "^1.0.0"
picocolors "^1.1.1"
"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.2":
version "7.25.2"
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz"
integrity sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==
"@babel/core@^7.17.7":
version "7.25.2"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz"
integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.24.7"
"@babel/generator" "^7.25.0"
"@babel/helper-compilation-targets" "^7.25.2"
"@babel/helper-module-transforms" "^7.25.2"
"@babel/helpers" "^7.25.0"
"@babel/parser" "^7.25.0"
"@babel/template" "^7.25.0"
"@babel/traverse" "^7.25.2"
"@babel/types" "^7.25.2"
"@babel/compat-data@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629"
integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==
"@babel/core@^7.29.6":
version "7.29.6"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.6.tgz#2f2c2ca1728ae73c9b68ece91d1ae6dee18ff83a"
integrity sha512-QdxmAo/ikZqqRGA8s43ww8lcql6naWRvEz0FFrl6MIlc7Gi6TroXnSdWa5U/kq6fzcpqpHesicQxFZIieZbyIA==
dependencies:
"@babel/code-frame" "^7.29.0"
"@babel/generator" "^7.29.6"
"@babel/helper-compilation-targets" "^7.28.6"
"@babel/helper-module-transforms" "^7.28.6"
"@babel/helpers" "^7.29.2"
"@babel/parser" "^7.29.3"
"@babel/template" "^7.28.6"
"@babel/traverse" "^7.29.0"
"@babel/types" "^7.29.0"
"@jridgewell/remapping" "^2.3.5"
convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.3"
semver "^6.3.1"
"@babel/generator@^7.25.0":
version "7.25.0"
resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz"
integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==
"@babel/generator@^7.29.6", "@babel/generator@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3"
integrity sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==
dependencies:
"@babel/types" "^7.25.0"
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^2.5.1"
"@babel/parser" "^7.29.7"
"@babel/types" "^7.29.7"
"@jridgewell/gen-mapping" "^0.3.12"
"@jridgewell/trace-mapping" "^0.3.28"
jsesc "^3.0.2"
"@babel/helper-annotate-as-pure@^7.24.7":
version "7.24.7"
@ -78,14 +68,14 @@
"@babel/traverse" "^7.24.7"
"@babel/types" "^7.24.7"
"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.24.7", "@babel/helper-compilation-targets@^7.24.8", "@babel/helper-compilation-targets@^7.25.2":
version "7.25.2"
resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz"
integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==
"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.24.7", "@babel/helper-compilation-targets@^7.24.8", "@babel/helper-compilation-targets@^7.25.2", "@babel/helper-compilation-targets@^7.28.6":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz#7a1def704302401c47f64fa85589e974ae217042"
integrity sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==
dependencies:
"@babel/compat-data" "^7.25.2"
"@babel/helper-validator-option" "^7.24.8"
browserslist "^4.23.1"
"@babel/compat-data" "^7.29.7"
"@babel/helper-validator-option" "^7.29.7"
browserslist "^4.24.0"
lru-cache "^5.1.1"
semver "^6.3.1"
@ -122,6 +112,11 @@
lodash.debounce "^4.0.8"
resolve "^1.14.2"
"@babel/helper-globals@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz#f04a96fbd8473241b1079243f5b3f03a3010ab7b"
integrity sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==
"@babel/helper-member-expression-to-functions@^7.24.8":
version "7.24.8"
resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz"
@ -138,15 +133,22 @@
"@babel/traverse" "^7.24.7"
"@babel/types" "^7.24.7"
"@babel/helper-module-transforms@^7.24.7", "@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.25.0", "@babel/helper-module-transforms@^7.25.2":
version "7.25.2"
resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz"
integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==
"@babel/helper-module-imports@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz#ef25048a518e828d7393fac5882ddd73921d7396"
integrity sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==
dependencies:
"@babel/helper-module-imports" "^7.24.7"
"@babel/helper-simple-access" "^7.24.7"
"@babel/helper-validator-identifier" "^7.24.7"
"@babel/traverse" "^7.25.2"
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/helper-module-transforms@^7.24.7", "@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.28.6", "@babel/helper-module-transforms@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz#b062747a5997ba138637201328bbff77960574ae"
integrity sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==
dependencies:
"@babel/helper-module-imports" "^7.29.7"
"@babel/helper-validator-identifier" "^7.29.7"
"@babel/traverse" "^7.29.7"
"@babel/helper-optimise-call-expression@^7.24.7":
version "7.24.7"
@ -160,6 +162,11 @@
resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz"
integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==
"@babel/helper-plugin-utils@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz#c0a0766f1a13617d8a17407d7ab8f9d486225ea4"
integrity sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==
"@babel/helper-remap-async-to-generator@^7.24.7", "@babel/helper-remap-async-to-generator@^7.25.0":
version "7.25.0"
resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz"
@ -194,31 +201,26 @@
"@babel/traverse" "^7.24.7"
"@babel/types" "^7.24.7"
"@babel/helper-string-parser@^7.24.8":
version "7.24.8"
resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz"
integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==
"@babel/helper-string-parser@^7.25.9":
version "7.25.9"
resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz"
integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
"@babel/helper-validator-identifier@^7.24.7":
version "7.24.7"
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz"
integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==
"@babel/helper-string-parser@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f"
integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==
"@babel/helper-validator-identifier@^7.25.9":
version "7.25.9"
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz"
integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
"@babel/helper-validator-identifier@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2"
integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==
"@babel/helper-validator-option@^7.24.8":
version "7.24.8"
resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz"
integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==
"@babel/helper-validator-option@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz#cf315be940213b354eb4abcc0bd01ebe3f73bc2a"
integrity sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==
"@babel/helper-wrap-function@^7.25.0":
version "7.25.0"
resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz"
@ -228,37 +230,20 @@
"@babel/traverse" "^7.25.0"
"@babel/types" "^7.25.0"
"@babel/helpers@^7.25.0":
version "7.26.10"
resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz"
integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==
dependencies:
"@babel/template" "^7.26.9"
"@babel/types" "^7.26.10"
"@babel/highlight@^7.24.7":
version "7.24.7"
resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz"
integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==
dependencies:
"@babel/helper-validator-identifier" "^7.24.7"
chalk "^2.4.2"
js-tokens "^4.0.0"
picocolors "^1.0.0"
"@babel/parser@^7.25.0", "@babel/parser@^7.25.3":
version "7.25.3"
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz"
integrity sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==
"@babel/helpers@^7.29.2":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.7.tgz#45abfde7548997e34376c3e69feb475cffb4a607"
integrity sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==
dependencies:
"@babel/types" "^7.25.2"
"@babel/template" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/parser@^7.26.9":
version "7.26.10"
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz"
integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==
"@babel/parser@^7.29.3", "@babel/parser@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334"
integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==
dependencies:
"@babel/types" "^7.26.10"
"@babel/types" "^7.29.7"
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.3":
version "7.25.3"
@ -627,14 +612,14 @@
"@babel/helper-simple-access" "^7.24.7"
"@babel/plugin-transform-modules-systemjs@^7.25.0":
version "7.25.0"
resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz"
integrity sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz#e575dd2ab9882906de120ff7dc9dee9914d8b6f3"
integrity sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==
dependencies:
"@babel/helper-module-transforms" "^7.25.0"
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/helper-validator-identifier" "^7.24.7"
"@babel/traverse" "^7.25.0"
"@babel/helper-module-transforms" "^7.29.7"
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/helper-validator-identifier" "^7.29.7"
"@babel/traverse" "^7.29.7"
"@babel/plugin-transform-modules-umd@^7.24.7":
version "7.24.7"
@ -946,53 +931,35 @@
dependencies:
regenerator-runtime "^0.14.0"
"@babel/template@^7.24.7", "@babel/template@^7.25.0":
version "7.25.0"
resolved "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz"
integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==
dependencies:
"@babel/code-frame" "^7.24.7"
"@babel/parser" "^7.25.0"
"@babel/types" "^7.25.0"
"@babel/template@^7.26.9":
version "7.26.9"
resolved "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz"
integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==
dependencies:
"@babel/code-frame" "^7.26.2"
"@babel/parser" "^7.26.9"
"@babel/types" "^7.26.9"
"@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.1", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.3":
version "7.25.3"
resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz"
integrity sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==
dependencies:
"@babel/code-frame" "^7.24.7"
"@babel/generator" "^7.25.0"
"@babel/parser" "^7.25.3"
"@babel/template" "^7.25.0"
"@babel/types" "^7.25.2"
"@babel/template@^7.24.7", "@babel/template@^7.25.0", "@babel/template@^7.28.6", "@babel/template@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700"
integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==
dependencies:
"@babel/code-frame" "^7.29.7"
"@babel/parser" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.1", "@babel/traverse@^7.25.3", "@babel/traverse@^7.29.0", "@babel/traverse@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d"
integrity sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==
dependencies:
"@babel/code-frame" "^7.29.7"
"@babel/generator" "^7.29.7"
"@babel/helper-globals" "^7.29.7"
"@babel/parser" "^7.29.7"
"@babel/template" "^7.29.7"
"@babel/types" "^7.29.7"
debug "^4.3.1"
globals "^11.1.0"
"@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.4.4":
version "7.25.2"
resolved "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz"
integrity sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==
dependencies:
"@babel/helper-string-parser" "^7.24.8"
"@babel/helper-validator-identifier" "^7.24.7"
to-fast-properties "^2.0.0"
"@babel/types@^7.26.10", "@babel/types@^7.26.9":
version "7.26.10"
resolved "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz"
integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==
"@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.29.0", "@babel/types@^7.29.7", "@babel/types@^7.4.4":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92"
integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==
dependencies:
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/helper-string-parser" "^7.29.7"
"@babel/helper-validator-identifier" "^7.29.7"
"@csstools/css-parser-algorithms@^3.0.0":
version "3.0.0"
@ -1098,6 +1065,14 @@
resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz"
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
"@jridgewell/gen-mapping@^0.3.12":
version "0.3.13"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.0"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/gen-mapping@^0.3.5":
version "0.3.5"
resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz"
@ -1107,6 +1082,14 @@
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/remapping@^2.3.5":
version "2.3.5"
resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1"
integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==
dependencies:
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.2"
resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz"
@ -1130,6 +1113,11 @@
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz"
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
"@jridgewell/sourcemap-codec@^1.5.0":
version "1.5.5"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
version "0.3.25"
resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz"
@ -1138,6 +1126,14 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@jridgewell/trace-mapping@^0.3.28":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0"
integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@ministryofjustice/frontend@^3.3.0":
version "3.3.0"
resolved "https://registry.npmjs.org/@ministryofjustice/frontend/-/frontend-3.3.0.tgz"
@ -1489,13 +1485,6 @@ ansi-regex@^6.0.1:
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz"
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
@ -1712,6 +1701,11 @@ balanced-match@^2.0.0:
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz"
integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==
baseline-browser-mapping@^2.10.12:
version "2.10.37"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz#3e636475b6b293244e2b23e2c71a2ab9d9e6ba7d"
integrity sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==
baseline-browser-mapping@^2.9.0:
version "2.9.19"
resolved "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz"
@ -1747,7 +1741,7 @@ braces@^3.0.3, braces@~3.0.2:
dependencies:
fill-range "^7.1.1"
browserslist@^4.23.1, browserslist@^4.23.3, browserslist@^4.28.1:
browserslist@^4.23.3, browserslist@^4.28.1:
version "4.28.1"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz"
integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==
@ -1758,6 +1752,17 @@ browserslist@^4.23.1, browserslist@^4.23.3, browserslist@^4.28.1:
node-releases "^2.0.27"
update-browserslist-db "^1.2.0"
browserslist@^4.24.0:
version "4.28.2"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.2.tgz#f50b65362ef48974ca9f50b3680566d786b811d2"
integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==
dependencies:
baseline-browser-mapping "^2.10.12"
caniuse-lite "^1.0.30001782"
electron-to-chromium "^1.5.328"
node-releases "^2.0.36"
update-browserslist-db "^1.2.3"
buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz"
@ -1791,14 +1796,10 @@ caniuse-lite@^1.0.30001759:
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz"
integrity sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==
chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
caniuse-lite@^1.0.30001782:
version "1.0.30001799"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz#5c909138c27f1a61219d3e092071c1cc7d32dc55"
integrity sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==
chalk@^4.0.0:
version "4.1.2"
@ -1837,13 +1838,6 @@ clone-deep@^4.0.1:
kind-of "^6.0.2"
shallow-clone "^3.0.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
@ -1851,11 +1845,6 @@ color-convert@^2.0.1:
dependencies:
color-name "~1.1.4"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
@ -2087,6 +2076,11 @@ electron-to-chromium@^1.5.263:
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz"
integrity sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==
electron-to-chromium@^1.5.328:
version "1.5.375"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.375.tgz#54a9a616dc2b3765e7263d98d14c2135408954d9"
integrity sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q==
element-closest@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/element-closest/-/element-closest-2.0.2.tgz"
@ -2253,11 +2247,6 @@ escalade@^3.2.0:
resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz"
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
@ -2555,9 +2544,9 @@ fast-levenshtein@^2.0.6:
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
fast-uri@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz"
integrity sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==
version "3.1.2"
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.2.tgz#8af3d4fc9d3e71b11572cc2673b514a7d1a8c8ec"
integrity sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==
fastest-levenshtein@^1.0.12, fastest-levenshtein@^1.0.16:
version "1.0.16"
@ -2864,11 +2853,6 @@ has-bigints@^1.0.1, has-bigints@^1.0.2:
resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz"
integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz"
integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
@ -3264,10 +3248,10 @@ js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
jsesc@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
jsesc@~0.5.0:
version "0.5.0"
@ -3529,10 +3513,10 @@ mutation-observer-inner-html-shim@^1.0.0:
resolved "https://registry.npmjs.org/mutation-observer-inner-html-shim/-/mutation-observer-inner-html-shim-1.0.1.tgz"
integrity sha512-YmJPDSUWJgBhwqRJP6AMvjdfDHU1gsrT5YdgpxMit2+x1khLYhdYq9fvp4clPsYecVT3JOprBf/KjEX7IqlU+g==
nanoid@^3.3.7:
version "3.3.8"
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz"
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
nanoid@^3.3.12:
version "3.3.12"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.12.tgz#ab3d912e217a6d0a514f00a72a16543a28982c05"
integrity sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==
natural-compare@^1.4.0:
version "1.4.0"
@ -3549,6 +3533,11 @@ node-releases@^2.0.27:
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz"
integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==
node-releases@^2.0.36:
version "2.0.47"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.47.tgz#521bb2786da8eb140b748841c0b3b3a75334ffc4"
integrity sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
@ -3739,7 +3728,7 @@ path-type@^4.0.0:
resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
picocolors@^1.0.0, picocolors@^1.0.1:
picocolors@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz"
integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
@ -3841,13 +3830,13 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.4.33, postcss@^8.4.41:
version "8.4.41"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz"
integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==
version "8.5.15"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.15.tgz#d1eaf677a324e9ec02196da2d3fecf4a0b9a735c"
integrity sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==
dependencies:
nanoid "^3.3.7"
picocolors "^1.0.1"
source-map-js "^1.2.0"
nanoid "^3.3.12"
picocolors "^1.1.1"
source-map-js "^1.2.1"
preact@^8.3.1:
version "8.5.3"
@ -4237,11 +4226,16 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.2.0:
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1:
version "1.2.0"
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz"
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
source-map-support@~0.5.20:
version "0.5.21"
resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz"
@ -4472,13 +4466,6 @@ stylelint@^16.8.2:
table "^6.8.2"
write-file-atomic "^5.0.1"
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
supports-color@^7.0.0, supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"
@ -4553,11 +4540,6 @@ text-table@^0.2.0:
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz"
integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz"
@ -4679,7 +4661,7 @@ unicode-property-aliases-ecmascript@^2.0.0:
resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz"
integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==
update-browserslist-db@^1.2.0:
update-browserslist-db@^1.2.0, update-browserslist-db@^1.2.3:
version "1.2.3"
resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz"
integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==

Loading…
Cancel
Save