Conversation
This pull request introduces several significant UI and workflow improvements, focusing on navigation, theming, and utility enhancements. The most substantial changes are a complete redesign of the navigation bar for better mobile and desktop experience, a new GitHub workflow for automated environment tagging, and a broad update to the application's theme and utility classes. **Navigation and Layout Improvements:** * [`components/AppNavbar.vue`](diffhunk://#diff-f5338ad3123fd5a2307b41d35b44ef3937f97b2cda5baae203d5dc80ba75109bL2-R411): Completely redesigned the navigation bar to support a responsive side-drawer menu on mobile devices, improved accessibility (keyboard navigation, focus management, Escape key support), and enhanced user profile/logout handling for both mobile and desktop. Updated branding and navigation links for clarity and consistency. * [`components/AppLogo.vue`](diffhunk://#diff-a3b5a2eb30621c64c88a28b95b89e1365ae22a64bdf983e2e137aba7657405f7L2-R2): Updated the logo image to use the new `tdei-logo.png` for consistent branding. **Theming and Utility Enhancements:** * `assets/scss/theme.scss`, `assets/scss/main.scss`: Overhauled theme variables with new brand colors, font families, and utility classes. Added CSS variables for colors and fonts, improved autofill handling, and added utility classes for common UI elements (headers, subtitles, hints). [[1]](diffhunk://#diff-3ca8bb2feea4e836c15314e7883fb708c1fbfac9be89b932659ac5be31b80823L6-R27) [[2]](diffhunk://#diff-6e542e5567dc3568b6ef395995b5897822a3f74fa7e12afcb413bc89f4347fd3R3-R41) [[3]](diffhunk://#diff-6e542e5567dc3568b6ef395995b5897822a3f74fa7e12afcb413bc89f4347fd3R75-R81) [[4]](diffhunk://#diff-6e542e5567dc3568b6ef395995b5897822a3f74fa7e12afcb413bc89f4347fd3R102-R141) **Workflow Automation:** * [`.github/workflows/tag.yml`](diffhunk://#diff-84dff8d1094ca39c02ac0e48d951ca22f4da29c76b50ae517f5bd2d50f94c2f6R1-R42): Added a new GitHub Actions workflow to automatically update environment tags (`dev`, `stage`, `prod`) when pull requests are merged into `develop`, `staging`, or `production` branches. **Component and UI Tweaks:** * [`components/DatasetTypeRadio.vue`](diffhunk://#diff-22e341fbd37362852438041432b32c9f56118cc6a4a423e1db60c575931ebad7L12-R12): Changed dataset type radio button styles from `btn-outline-primary` to `btn-outline-secondary` for better visual consistency with the new theme. [[1]](diffhunk://#diff-22e341fbd37362852438041432b32c9f56118cc6a4a423e1db60c575931ebad7L12-R12) [[2]](diffhunk://#diff-22e341fbd37362852438041432b32c9f56118cc6a4a423e1db60c575931ebad7L23-R23) * [`components/AppSpinner.vue`](diffhunk://#diff-3e0e987214827beb4127b0ef553325bf67336549c3f44babee21a6f932bada0cL2-R5): Minor markup adjustment for improved readability and accessibility. * [`components/AppImageViewer.vue`](diffhunk://#diff-6c814aa23949b1b55654ff0e34e7e3d974a5ef891952f1a949067897bef36bb4R1-R38): Added a new wrapper component for image viewing with customizable toolbar options.
Introduce a workspace projects feature: new page pages/workspace/[id]/projects.vue with grid/list views, search, status/sort filters, pagination, view toggle and create action. Add reusable AppSelect component and project presentation components (ProjectCard, ProjectListRow, StatusBadge) and related scoped SCSS. Add styles and utility classes in assets/scss/main.scss. Add mock API fixtures (mock-api/workspace-projects/page-1..3.json), new services (services/projects.ts, services/mock-workspace-projects.ts), update services/index.ts and workspaces service to wire clients, and add types/projects.ts. Also update dashboard toolbar to include a link to the new Projects page.
Add useProjectDisplay composable to centralize computed properties for projects (progressPercent, completedTaskCount, taskSummary, createdDate) and reduce duplication. Update ProjectCard.vue and ProjectListRow.vue to use the new composable. Also fix status label typo from "Inprogress" to "In Progress" in StatusBadge.vue and the status options in pages/workspace/[id]/projects.vue to keep UI text consistent.
Convert hard-coded px values to rem in main.scss for consistent, scalable typography and spacing (page headers, names, hints, and inline filters). Add unprefixed line-clamp properties alongside existing -webkit- prefixed rules in ProjectCard.vue to improve multi-line truncation for title and summary. Replace a computed wrapper with a plain typed array for statusOptions in pages/workspace/[id]/projects.vue to remove unnecessary reactivity for a static list.
Add SVG assets for project and status icons (project, completed, inprogress, draft) and switch UI to use them. ProjectCard now imports and displays project.svg (replacing the app-icon) and adjusts icon sizing/CSS. StatusBadge now imports and returns SVG sources instead of text glyphs and includes image sizing and subtle badge color/border tweaks. Also remove unused completedTaskCount destructuring from ProjectCard and ProjectListRow (cleanup).
Introduce a complete project creation wizard: adds MapPreview (maplibre-gl) and Stepper components, individual step components (Details, Area, Tasks, Settings, Review), and corresponding SVG assets for status icons. Adds a composable (useProjectWizard) to manage wizard state with localStorage persistence, step loading, navigation, and project creation flow (including name availability check). Includes a step configuration JSON, new project-wizard services and types, and a mock client for development. Also adds the create project page and moves projects list page to index.vue; updates services/index.ts accordingly. MapPreview supports styled feature layers and camera padding for responsive layout.
Multiple fixes and improvements across wizard, services, and UI: - components/AppSelect.vue: add optionRefs, set refs for options, clear refs when closed, and scroll active option into view on arrow key navigation. - composables/useProjectDisplay.ts: parse createdAt as Date before calling toLocaleDateString to avoid errors with string timestamps. - composables/useProjectWizard.ts: fix watch dependencies to include the draft ref directly. - pages/workspace/[id]/projects/create.vue: adjust map padding values. - services/index.ts: pass tdeiClient into ProjectWizardClient construction. - services/project-wizard-definitions.ts: add parseStepDefinitions to validate step config (ids and titles) and use it to derive STEP_DEFINITIONS and step list. - services/project-wizard.ts: inject TDEI client into ProjectWizardClient, expose auth, ensure cloned instances carry tdeiClient, set auth header, and refresh auth before requests by overriding _send. - services/projects.ts: ensure unknown statuses default to 'draft' and log a warning. These changes add configuration validation, proper auth handling for wizard requests, UI accessibility/keyboard behavior improvements, and minor bug fixes.
Introduce an interactive Area of Interest flow: add AoiGeometryMap component (maplibre-based) with drawing, vertex editing, preview and fit behavior; replace MapPreview with the new component. Add file import UI and drag/drop in AreaOfInterestStep, with upload/reset/draw actions, warnings and error messaging, plus new SVG icons and related styles. Update theme with danger color, clear sample features in step config and add localized content/labels and upload thresholds. Wire step events into project create page and add supporting AOI service/types/payload/definitions.
Render and manage draft polygon geometries in the project wizard map and centralize AOI logic into a composable. AoiGeometryMap.vue: add Polygon typing, new draft polygon fill/line layer ids and layer specs, include them when creating map layers, build draft polygon features from pending + preview vertices, call syncDraftPolygon on relevant events and emit update:aoi for draft polygons. Introduce composable useProjectWizardAoi.ts to encapsulate area import, draw-mode state, warnings, and helper actions. pages/workspace/[id]/projects/create.vue: replace in-component AOI handling with the new composable, removing duplicated import/parse/area logic.
Introduce task-generation features for the project wizard: add a new TasksGenerationStep UI with range control, generate/reset actions and summary display; add a composable (useProjectWizardTasks) to manage task preview, generation state and interactions; implement task preview generation logic and helpers in services/project-wizard-tasks (grid building, projection math, area normalization and simulated generation); extend types with task preview feature/summary definitions. Wire the preview grid into the AOI map by adding a new GeoJSON source and fill/line layers in AoiGeometryMap.vue and ensure map updates only after style load. Integrate the composable into the create project page, disable navigation while generating, initialize default task area in draft, and expose computed displayedTaskGrid for map rendering.
Introduce settings functionality for the project wizard: add AssignUsersField and RichTextEditor components, and a useProjectWizardSettings composable. SettingsStep.vue updated to include lock timeout, review toggle, validator assignment (uses AssignUsersField) and instructions (uses RichTextEditor with TipTap). The composable handles loading workspace users, maintaining selected validators in the draft, and exposes helpers to add/remove validators and update instructions/timeout/review flags. package.json updated to include TipTap editor dependencies and related services/types were extended to support workspace user listing and payload adjustments.
Add a review summary flow and status dialog for project creation. Introduces a new project-wizard-review service to build/sanitize review summaries, updates ReviewStep to display summary including rich text instructions, and extracts rich-text styles to global SCSS while adding URL validation in the RichTextEditor. Implement project creation result handling across the client, composables and pages (including a mock create implementation), adjust payload/role-assignment shapes, and add a reusable StatusDialog component to surface success/error outcomes and navigation actions.
Persist a created-project checkpoint in the wizard state and shift task generation to a post-creation flow. Added createdProject state to composables/useProjectWizard, prevented navigating back after creation, locked step selection, and updated UI labels/controls to reflect creation-first workflow. Moved task generation out of the local draft into useProjectWizardTasks and ProjectWizardClient (with mock implementation), added watches to reset/validate generated summaries, and updated dialogs to handle create vs. task-generation errors. Also reordered/cleaned step config, removed embedded tasks draft fields, hid review task count until generation, and improved AOI map viewport handling to avoid redundant camera moves by normalizing centers and applying tolerances. Files touched: components (map, stepper, steps), composables, services (client & mock), data config, pages, and types.
Switch clients and services to the tasking API and normalize request/response shapes to snake_case and new feature formats. Key changes: use taskingApiUrl when constructing workspace/project clients; implement ProjectWizardClient methods (list users, name check, create project, generate tasks) and normalize created project responses; add conversion utilities for task area <-> cell size and include generated task grid in task generation summary and types. Update front-end composables and pages to expose and prefer generatedTaskGrid, adjust AOI map layer styles/filters for generated grids, and add minor CSS fixes for ProjectCard layout. Update mock workspace-projects to normalize legacy fixtures to the new API shape and adjust pagination/sorting/query param names across WorkspaceProjectsClient and related types. Note: API field names and some types changed (e.g. ProjectWizardCreatePayload, workspace projects fields), so callers should use the new snake_case payloads and expect the updated response structures.
Show a "Project created successfully" callout in the Tasks step and pass a createdProjectName prop (falls back to draft name). Add clearPersistedState() in the project wizard composable (wraps clearStoredState) and use a new leaveWizard helper to clear persisted draft state before navigating away from the wizard. Replace several clearDraft() usages with clearPersistedState to ensure stored wizard state is removed when exiting.
…ther miscellaneous changes remove task generation from the project setup wizard and delete the old wizard-only task generation components/composables add a dedicated project details page for /projects/[projectId] with overview, instructions, tasks, and contributions tabs add zero-task task setup flow in the Tasks tab with separate generate and save steps persist generated grids through the task save API and refresh project state from real backend data add real map-based AOI and task grid rendering with selectable tasks, status colors, lock markers, and legend add task list selection/filtering UI and task setup panel styling updates replace mock project detail usage with real API-backed project data on the details page extend project and task typings plus project service methods to support AOI, task list, and task status normalization clean up service typing by removing any from the projects client override update env/package/config files required for the new tasking flow
add tasking API env flags and real-data config defaults update AOI upload warning threshold to 5000 square kilometers tighten project/task typings for API responses switch save-tasks CTA to a clearer non-download icon show 6 tasks per page and fix lock menu clipping in tasks list collapse long pagination ranges with ellipses for large task counts
Minor update to readme file
Rapid simple integration done. This is WIP
… deletion. Added contributors tab
- add project role resolution composable and project-role lookup support - gate project creation, contributor management, tabs, and task actions by user role - capture Rapid upload changeset IDs and submit them with completed mapping - switch workspace/project flows to the new tasking API env configuration - add frontend guidelines doc and minor project detail UI/map cleanup
## PR Summary This PR implements the end-to-end user flows for Workspace Projects under the TDEI Tasking Manager. It establishes: 1. **Projects Directory View** (Grid/List directory of active and draft projects). 2. **Project Creation Wizard** (A multi-step setup flow featuring GeoJSON uploads, interactive AOI drawing, dynamic task grid generation, and validator assignment). 3. **Project Details Dashboard** (Tabs for Overview, Tasks list, and Contributions, driven by an interactive MapLibre map). 4. **Task Mapping Editor** (An interactive editor screen wrapping changeset submissions, locking controls, and peer review submissions). 5. **Granular Project-Level Access Controls** (Explicit role delegation for Leads, Validators, and Contributors). ## Key Features & Changes Implemented ### 1. Project Directory Page (Dashboard) * **Components:** [ProjectCard.vue], [ProjectListRow.vue], [StatusBadge.vue] * **Pages:** [index.vue (Workspace Projects)] * **Details:** * Displays all projects in a workspace with card (grid) and row (list) layouts. * Supports real-time text searches, sorting (by update time, name, or status), and cursor-based pagination. * Filters projects dynamically by status (Draft, In Progress, Completed). ### 2. Multi-Step Project Wizard * **Components:** [Stepper.vue], [AoiGeometryMap.vue], [AssignUsersField.vue], [RichTextEditor.vue] * **Pages:** [create.vue (Project Wizard)] * **Steps Included:** * **Step 1 (Project Details):** Defines name, description, and TMS Imagery URL. * **Step 2 (Area of Interest - AOI):** Provides draw controls (polygons/rectangles) and supports uploading GeoJSON/Shapefiles with dynamic KM² area calculations. * **Step 3 (Settings & Task Generation):** Defines task bounding sizes (dynamic grid calculation), task lock timeouts, and peer review settings. Enables assigning workspace members as project-level validators. * **Step 4 (Review & Launch):** Displays a full visual review card of setup variables before publishing to the backend. Includes an HTML-sanitized rendering of markdown instructions. ### 3. Project Detail Page & Interactive Map * **Components:** [ProjectMap.vue], [TasksTab.vue], [ContributionsTab.vue], [TaskSetupPanel.vue] * **Pages:** [index.vue (Project Detail)] * **Details:** * Integrates a MapLibre GL map that dynamically loads task grids and status-colored polygons. * Implements **z-index sorting** on map rendering so the selected task is drawn on top. * Adjusts opacity contrast * Connects the sidebar Task List selection with the interactive map selection. ### 4. Task Mapping Editor * **Pages:** [editor.vue (Task Editor)] * **Details:** * Integrates changeset tracking using the Rapid editor wrapper. * Implements lock status handling (lock, force unlock, task timeout timer). * Implements changeset submission checks and feedback forms (rejection categories/notes). ### 5. Access Control & Contributor Roles * **Details:** * Restricts access using an effective role precedence model: Workspace Lead (always has owner/lead controls), Project Lead, Validator, or Mapper. * Enables Project/Workspace Leads to delegate project roles, manage contributors list with search, filter, pagination, and update permissions dynamically. --- ### Testing Scenarios #### 1. Project Directory & Layouts 1. Navigate to `/workspace/:id/projects`. 2. Toggle between grid and list layouts. Ensure column headers, labels, and status badges align properly. 3. Test search filter input and status dropdown options to confirm correct filtering of projects. #### 2. Project Creation Stepper 1. Click **Create Project** to launch the wizard. 2. Complete Step 1 (Details). 3. On Step 2 (AOI), upload a valid GeoJSON file or draw a polygon on the map. Verify that the map updates with the geometry and computes the correct area. 4. On Step 3, drag/adjust the grid size and verify task grid generation visual previews. 5. Launch the project, and ensure the draft is cleared and redirected to the detail page. #### 3. Task Mapping & Lock Verification 1. On the Project details page, click on a task polygon in the map or choose a task from the list. 2. Verify that the task is highlighted with a thick dark border and high opacity contrast. 3. Click **Lock Task**, and confirm that a lock icon appears on the map task center and the panel shows the task timer. 4. Access the task editor at `/workspace/:id/projects/:projectId/tasks/:taskId/editor` to verify the Rapid integration and changeset submission controls. --- ### Screenshots: <img width="1470" height="832" alt="Screenshot 2026-06-26 at 8 50 14 PM" src="https://github.com/user-attachments/assets/e1e5fbb9-c987-4c50-be0a-23be319b9f26" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 50 26 PM" src="https://github.com/user-attachments/assets/ef78ac3e-3a2a-488e-9b3f-e44817cfebcb" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 50 57 PM" src="https://github.com/user-attachments/assets/06060514-999a-4ef9-b1bd-57d61145f97f" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 51 43 PM" src="https://github.com/user-attachments/assets/cd7d2134-661d-44cc-b061-ee5a67ba3851" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 51 49 PM" src="https://github.com/user-attachments/assets/6889eb4e-705f-45b9-aee3-d5fbd6fef38b" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 52 13 PM" src="https://github.com/user-attachments/assets/e324e53d-79d2-4d10-963c-186443694ca3" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 52 33 PM" src="https://github.com/user-attachments/assets/0bcc76be-30e6-407f-ab42-b5ea357e01ee" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 52 39 PM" src="https://github.com/user-attachments/assets/b6605b3a-8a69-45b2-bb87-a891347e6dc3" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 52 59 PM" src="https://github.com/user-attachments/assets/5c9a90fe-ec88-4610-895c-0dd81cd9baa0" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 53 21 PM" src="https://github.com/user-attachments/assets/4ad95b11-cbcc-4098-afc4-9e68835c12fd" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 53 36 PM" src="https://github.com/user-attachments/assets/4afb841a-9a67-41c9-a7fb-4a03abf16405" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 54 05 PM" src="https://github.com/user-attachments/assets/878a656d-1591-4732-9c11-8b2687b37711" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 54 23 PM" src="https://github.com/user-attachments/assets/2516d1f3-cc9a-48ca-a715-c2289908d8ab" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 54 37 PM" src="https://github.com/user-attachments/assets/a26bbf51-3e55-4f9d-bd1d-67eacda84d43" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 55 24 PM" src="https://github.com/user-attachments/assets/e9f2c846-1b82-427e-8598-e55002b1604e" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 55 44 PM" src="https://github.com/user-attachments/assets/65d480d4-4a91-49a7-9e49-a0209483d11d" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 55 51 PM" src="https://github.com/user-attachments/assets/9f7801ea-32fa-45e4-9a24-63ab463cceb6" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 57 39 PM" src="https://github.com/user-attachments/assets/6d1d109d-d210-4c4a-a5eb-048ca3dd0971" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 57 45 PM" src="https://github.com/user-attachments/assets/9c47e581-a8a4-4862-b310-fe53a46e3ba9" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 58 25 PM" src="https://github.com/user-attachments/assets/73ada906-664a-430d-b7ff-8e50f1f08063" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 58 39 PM" src="https://github.com/user-attachments/assets/49b9d71e-adbe-4806-8363-3ca045c4098e" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 58 44 PM" src="https://github.com/user-attachments/assets/aa1a9172-1b3c-4096-aeee-39bbff2dabb0" /> <img width="1470" height="956" alt="Screenshot 2026-06-26 at 8 59 22 PM" src="https://github.com/user-attachments/assets/38718ba3-a0ac-4214-a236-966a49223dbc" />
Fixes to build exceptions
Fixes to build exceptions
Adds the code that fixes the build generation issue
📝 WalkthroughWalkthroughUpdates Rapid manager logging and event handling in services/rapid.ts (typed userDetails callback, stablechange changesLength calculation, added console logs), expands Claude settings' allowed Bash commands, and removes a TODOs note from README. ChangesRapid Service and Tooling Updates
Estimated code review effort: 2 (Simple) | ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@services/rapid.ts`:
- Line 121: The `changesLength` calculation in `services/rapid.ts` is using
`||`, so it only returns the first non-zero bucket instead of the total pending
edits. Update the `changesLength` assignment in the `rapid` flow to sum
`changes.modified`, `changes.created`, and `changes.deleted` so
`normalizePendingEditCount` receives the full count. Keep the fix localized to
the `changesLength` variable used right before the pending edit normalization.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b6e6c1dc-ca49-4a0c-85ef-ffc6e962af97
📒 Files selected for processing (3)
.claude/settings.jsonREADME.mdservices/rapid.ts
💤 Files with no reviewable changes (1)
- README.md
| // this.#notifyStateChange(state); | ||
| const changes = editSystem.changes(); | ||
| console.log('Rapid editor changes', changes); | ||
| const changesLength = changes.modified.length || changes.created.length || changes.deleted.length; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
changesLength uses || instead of +, underreporting pending edits.
When multiple change categories are non-empty (e.g., 2 modified + 3 created), || returns only the first non-zero count (2), not the total (5). The downstream consumer normalizePendingEditCount treats this as the pending edit count shown to users, so the count will be wrong whenever edits span more than one category.
🐛 Proposed fix
- const changesLength = changes.modified.length || changes.created.length || changes.deleted.length;
+ const changesLength = changes.modified.length + changes.created.length + changes.deleted.length;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const changesLength = changes.modified.length || changes.created.length || changes.deleted.length; | |
| const changesLength = changes.modified.length + changes.created.length + changes.deleted.length; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@services/rapid.ts` at line 121, The `changesLength` calculation in
`services/rapid.ts` is using `||`, so it only returns the first non-zero bucket
instead of the total pending edits. Update the `changesLength` assignment in the
`rapid` flow to sum `changes.modified`, `changes.created`, and `changes.deleted`
so `normalizePendingEditCount` receives the full count. Keep the fix localized
to the `changesLength` variable used right before the pending edit
normalization.
.claude/settings.jsonto allow a few additionalBash(...)commands, includingvue-tsc,git add, and the@sentry/nuxtversion check.anytyping.services/rapid.tsto add logging around Rapid loading, editor change handling, and uploader success events, while also changing the stable change flow to calculate a combined change count before notifying state updates.