镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Software: Multi-version App Store apps for iOS, iPadOS, and Android - #54915

Merged
RachelElysia merged 10 commits into
feat/47904-multiple-app-instancesfrom
53641-app-store-app-versions
Oct 7, 2026
Merged

RachelElysia merged 10 commits into
feat/47904-multiple-app-instancesfrom
53641-app-store-app-versions

Conversation

@RachelElysia

@RachelElysia RachelElysia commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Related issue: Resolves #53641

Description

Adds the multi-version UI for App Store app titles (iOS/iPadOS/Android) on the software title page. Each version carries its own name, managed app configuration, label scope, self-service, and (iOS/iPadOS) auto-update window, so admins can give different host groups different app settings on the same fleet.

Library section (multi-version iOS/iPadOS/Android App Store titles only; macOS VPP and in-house .ipa keep today's single-row UI):

  • Grey app-metadata header above the accordion list: store icon + bold store name + version with "Updated every hour." tooltip (iOS) or AndroidLatestVersionWithTooltip (Android).
  • Per-version accordion rows with the admin's version name, file-configuration-profile icon, per-version status counts, self-service / auto-update / labels icons, expand → pencil + trash in the panel.
  • New Add version button on the description row (disabled at 10 versions, disabled in GitOps with the standard tooltip).

Modals:

  • Add version — Name (required, unique), Configuration (Editor; XML scaffold for iOS/iPadOS, {} for Android), Self-service (slider, iOS/iPadOS), Category + "View end user experience" link (iOS/iPadOS when self-service on), Auto updates with window times (iOS/iPadOS), Target with first-added-wins callout. Target defaults to Custom.
  • Edit version — same form, pre-filled. Three-way configuration save: unchanged scaffold = no change (field omitted), empty editor = clear (null for iOS/iPadOS, {} for Android to match the BE's clear values), content = set. IEditAppStoreAppFormData.configuration widened to accept null.
  • Delete version — title switches to "Delete version"; the "Custom icon and display name will be deleted." sentence appears on last-version delete.

Info card (SoftwareSummaryCard):

  • "Versions" count = admin-added versions on multi-version titles.
  • Actions dropdown collapses to a single pencil Edit (appearance) button (secondary variant).
  • Self-service and Auto-update chips suppressed (now per-version).

Shared-component refactors (used by this PR + VPP macOS edit):

  • CategoriesSelector extracted and exported from SoftwareOptionsSelector; new optional label and previewButtonLabel props so the version modal can use "Category" + "View end user experience" without changing the fleet-level copy.
  • DropdownTargetLabelSelector gained labelInfoBanner and error props so the version modal plugs the first-added-wins banner through the component and surfaces the labelTargets validation error, eliminating the duplicate "Target" wrapper.

Form validation fixes:

  • Shared getAutoUpdateWindowDurationMinutes helper (in pages/SoftwarePage/helpers) wrap-aware so overnight windows under one hour (e.g. 23:30-00:15) no longer bypass the ≥60 min check. Applied to both the version-form validator and EditAutoUpdateConfigModal/helpers.tsx.
  • Android configuration arrives as a parsed object from axios; versionToFormData stringifies before handing to the Editor. IAppStoreApp.configuration and IAppStoreAppVersion.configuration widened to string | Record<string, unknown>.

Backend contract: depends on #53320 (API + YAML) + #54479 (migration) + #54590 (endpoints) + #54908 (install precedence) + #54805 (GitOps). This PR targets the feat/47904-multiple-app-instances feature branch, not main; once that feature branch merges to main the API shape will match my FE types.

Follow-up PR: #54954 ships host detail configuration icon + version-name tooltip, activity feed per-version copy + edited-software details modal Version row + readonly configuration editor, iOS/iPadOS install details sheet config-update sentence, and Setup experience helper text per platform tab.

Known gap not in either PR: per-version /hosts filter wiring. BE has not added a VPPAppVersionID / AppStoreAppVersionID filter to HostListOptions (confirmed on 53639-app-store-app-version-precedence and 53640-app-store-apps-gitops branches). Current click-through on per-version status counts routes to the title-wide host filter; needs a new BE ticket to specify the query key.

Testing

  • Added automated tests across the touched suites (modal, cell, helper, and form coverage)
  • QA'd all new/changed functionality manually

Manual steps mirror the parent story's core flow:

  1. Open an iOS/iPadOS or Android App Store title in Library.
  2. Click Add version → add Production and Test versions with different configurations and label scopes.
  3. Verify two version rows render with their own counts + icons, grey header above shows the app metadata.
  4. Edit a version → change name, labels, auto-update window → save.
  5. Edit a version → delete the stored configuration bytes → confirm BE records the clear (null for iOS/iPadOS, {} for Android).
  6. Try an overnight auto-update window shorter than 60 minutes (e.g. 23:30 → 00:15) → confirm inline error, not a 400 from the backend.
  7. Delete the first-added version → confirm 2-sentence modal.
  8. Delete the last version → confirm the "Custom icon and display name will be deleted." sentence appears.
  9. Verify macOS VPP and in-house .ipa titles still render the single-row Library UI.

AI

AI: Claude Code (claude-opus-4-7)

Screenrecording

Note: Save buttons are disabled for editing because app_store_apps is null in my branch so it's hitting a FE validation for a bad id

Screen.Recording.2026-10-06.at.11.33.25.PM.mov
Screen.Recording.2026-10-06.at.11.32.35.PM.mov

GitOps mode disables saving edits

Screen.Recording.2026-10-06.at.11.40.15.PM.mov

@codecov

codecov Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 57.41325% with 135 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (feat/47904-multiple-app-instances@08aa50e). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...eDetailsPage/EditVersionModal/EditVersionModal.tsx 39.13% 42 Missing ⚠️
...etailsPage/VersionFormFields/VersionFormFields.tsx 61.11% 42 Missing ⚠️
...tleDetailsPage/AddVersionModal/AddVersionModal.tsx 37.09% 39 Missing ⚠️
frontend/services/entities/software.ts 10.00% 9 Missing ⚠️
...lsPage/SoftwareSummaryCard/SoftwareSummaryCard.tsx 85.71% 2 Missing ⚠️
...Page/LibraryItemAccordion/LibraryItemAccordion.tsx 91.66% 1 Missing ⚠️
Additional details and impacted files
@@                         Coverage Diff                          @@
##             feat/47904-multiple-app-instances   #54915   +/-   ##
====================================================================
  Coverage                                     ?   76.50%           
====================================================================
  Files                                        ?     4260           
  Lines                                        ?   259474           
  Branches                                     ?    15203           
====================================================================
  Hits                                         ?   198518           
  Misses                                       ?    60779           
  Partials                                     ?      177           
Flag Coverage Δ
frontend 69.35% <57.41%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

<Button
variant="subdued"
variant="secondary"
size="small"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

per figma

@RachelElysia
RachelElysia changed the base branch from main to feat/47904-multiple-app-instances October 7, 2026 01:47
Adds the versions-oriented UI on the software title page for iOS, iPadOS,
and Android App Store app titles. Each version carries its own name,
managed app configuration, label scope, self-service, and (iOS/iPadOS)
auto-update window, so admins can give different host groups different
settings without creating a separate fleet.

- Library section renders a grey app-metadata header (store icon + name +
  version with tooltip) above an accordion of per-version rows.
- Per-version rows show status counts, self-service / auto-update /
  labels icons on the header, expand to pencil + trash in the panel.
- New Add version button (disabled at 10 versions and in GitOps mode)
  opens a modal with Name, Configuration (XML/JSON Editor), Self-service,
  Category + preview end-user experience, Auto updates window, and
  Target (defaults to Custom on 2nd+ version with the first-added-wins
  callout).
- Edit version modal reuses the same form, pre-filled from the version.
- Delete version modal switches to "Delete version" title and surfaces
  the "Custom icon and display name will be deleted." sentence on the
  last-version delete.
- Info card: Versions count shows admin-added versions; Actions dropdown
  collapses to a single pencil Edit (appearance) button; Self-service
  and Auto-update chips suppressed (per-version now).
- macOS VPP and in-house .ipa titles keep the existing single-row UI.
- Migrate AddVersionModal and EditVersionModal to the shared
  `useFormValidation` hook (reference pattern per `UserForm`/`ApiUserForm`).
  Server-side name errors now render inline AND fire a toast.
- Wrap Target, Category, and Auto updates sections in `FormField`; drop
  the hand-rolled label styles.
- Use `vertical-form-layout`, `vertical-modal-layout`, and
  `flex-column-8px-gap` mixins instead of inline flex-column rules.
- Drop the dead `softwareTitleId` prop + hidden input from AddVersionModal
  (the add route targets the title via `app_store_id` + `platform`).
- `<b>` → `<strong>` in new copy.
- Hardcode `variant="secondary"` + `size="small"` on the single Edit
  (appearance) button; drop the `singleEditButtonVariant` prop.
- Rename `showCustomIconSentence` → `isDeletingCustomMetaData`.
- Trim verbose comments on config scaffolds and
  `MAX_APP_STORE_APP_VERSIONS_PER_TITLE`.
- Hoist `getAutoUpdateTooltip` to `pages/SoftwarePage/helpers.tsx` so
  SoftwareNameCell, SoftwareSummaryCard chip, and LibraryItemAccordion
  row icon all share the single copy that matches Figma.
- Row-level icon buttons use `size="small"` which renders subdued
  icon-only at 28x28 natively; dropped the scoped SCSS override.
- Drop the redundant Android platform check on the preview-end-user
  handler and modal — the button is already iOS/iPadOS-only inside
  VersionFormFields.
- Drop the Fleet Desktop tail from the Edit version toast — Fleet
  Desktop isn't the install surface for iOS/iPadOS or Android.
- Strip issue refs from comments; tighten remaining comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Core add and edit flows mishandle Android configuration, validation, auto-update persistence, and GitOps gating.

Review effort: Balanced
Findings: 3 High severity · 2 Medium severity · 1 Low severity

Open (6)
What changed in this PR

Adds multi-version App Store app management to the software title page.

Changes:

  • Adds per-version Library rows, metadata, counts, and actions.
  • Adds version creation, editing, deletion, targeting, and configuration forms.
  • Updates summary cards, shared UI components, types, mocks, and tests.
File Description
frontend/​services/​entities/​software.ts Adds version-aware API payloads and requests.
.../​VersionFormFields/​VersionFormFields.tsx Adds shared version form fields.
.../​VersionFormFields/​index.ts Exports version form utilities.
.../​VersionFormFields/​_styles.scss Styles shared version fields.
.../​SoftwareTitleDetailsPage.tsx Renders and manages version rows and modals.
.../​SoftwareSummaryCard/​SoftwareSummaryCard.tsx Shows version counts and per-version presentation.
.../​InstallerDetailsWidget/​InstallerDetailsWidget.tsx Supports custom graphics and hidden versions.
.../​LibraryItemAccordion/​LibraryItemAccordion.tsx Adds per-version actions and indicators.
.../​LibraryItemAccordion/​_styles.scss Adjusts badge spacing.
.../​helpers.tests.ts Updates fixtures for version arrays.
.../​EditVersionModal/​index.ts Exports the edit modal.
.../​EditVersionModal/​EditVersionModal.tsx Implements version editing.
.../​EditVersionModal/​EditVersionModal.tests.tsx Tests edit-modal rendering and validation.
.../​EditVersionModal/​_styles.scss Styles the edit modal.
.../​DeleteSoftwareModal/​DeleteSoftwareModal.tsx Adds version-specific deletion behavior.
.../​DeleteSoftwareModal/​DeleteSoftwareModal.tests.tsx Tests version deletion messaging.
.../​AddVersionModal/​index.ts Exports the add modal.
.../​AddVersionModal/​AddVersionModal.tsx Implements version creation.
.../​AddVersionModal/​AddVersionModal.tests.tsx Tests add-modal behavior.
.../​AddVersionModal/​_styles.scss Styles the add modal.
.../​SoftwareTitleDetailsPage/​_styles.scss Styles version headers and lists.
frontend/​pages/​SoftwarePage/​helpers.tsx Shares auto-update tooltip formatting.
.../​SoftwareDetailsSummary/​SoftwareDetailsSummary.tsx Updates the appearance-edit button.
frontend/​interfaces/​software.ts Adds version response types and limits.
.../​SoftwareNameCell/​SoftwareNameCell.tsx Reuses the shared auto-update tooltip.
frontend/​__mocks__/​softwareMock.ts Adds version-array defaults to mocks.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

- Add shared `validateVersionForm` helper covering name (required + unique),
  Custom target (at least one label), auto-update window (both times
  required, HH:MM format, >=60min or wrap-to-next-day), and Android JSON
  validity. Wire error display on Target FormField + window inputs.
- EditVersionModal: new `titleDisplayName` prop so Auto updates help text
  shows the app's display name instead of falling back to the admin's
  version label (e.g. "Production").
- Parse Android configuration as JSON before sending — backend strict-
  decodes `configuration` as an object, not a string.
- GitOps lock: wrap EditVersionModal Save button in
  `GitOpsModeTooltipWrapper` so GitOps-managed versions can't be PATCHed
  from the UI (parity with Add + Delete).
- Prettier fix on AgentOptionsPage.tests.tsx (unrelated — surfaced in CI).
- EditVersionModal: match Add on configuration (undefined = no change,
  not silent clear); block Save on shim-id (-1 sentinel so it can't
  collide with a real version id).
- Widen service `configuration` to string | Record<string, unknown> so
  Android's parsed JSON isn't double-cast at the call site.
- Hoist EMPTY_XML / EMPTY_JSON scaffolds + `getEmptyConfigScaffold` into
  VersionFormFields; drop the dupes from both modals.
- getAutoUpdateTooltip returns null when the window is incomplete.
- Coerce isSelfService / isAutoUpdateEnabled to bool at the row prop
  boundary.
- SoftwareSummaryCard: lift versionsCount above the early return so the
  installer-less branch reuses it.
- Trim stray blank line in LibraryItemAccordion scss.
- Add createMockAppStoreAppVersion factory; swap in-test literals.
- LibraryItemAccordion: smoke tests for the versions-layout row (name
  renders without store version chip, self-service + auto-update icons,
  auto-update icon hidden when off).
VersionFormFields:
- Swap the hand-rolled category block for the shared CategoriesSelector
  (exported from SoftwareOptionsSelector); pass label="Category" and
  previewButtonLabel="View end user experience" to override the shared
  defaults for the version modal.
- Drop the outer FormField name="target" wrapper that was rendering
  "Target" twice (once from the wrapper, once from the selector's own
  title). Plumb the InfoBanner and labelTargets error through new
  selector props instead.

CategoriesSelector (shared):
- Export the component (was module-private).
- Wrap in FormField so the label + preview button inherit the standard
  form-field envelope.
- Preview button always inline-right of the label (subdued/small),
  dropping the bottom-of-list bordered variant and its scss.
- New optional label / previewButtonLabel props for per-callsite copy.

DropdownTargetLabelSelector:
- New optional labelInfoBanner slot (renders between title and radios)
  and error prop (replaces the title text + applies --error class).
  Both default off, so other callers are unchanged.

LibraryItemAccordion row icons:
- Drop the textBalanced={false} opt-out so the Play Store / self-service
  tooltips get the TooltipWrapper balanced default.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Version configuration handling, add-time auto updates, validation, shim deletion, and accessibility contain unresolved defects.

Review effort: Balanced
Findings: 3 Medium severity

Open (3)
Resolved since last review (6)
Previously missed (3)

In code that hasn't changed since last review

Medium severity Persist auto-update settings when adding an app

frontend/​pages/​SoftwarePage/​SoftwareTitleDetailsPage/​AddVersionModal/​AddVersionModal.tsx:142

These auto-update values are silently ignored on add: the current POST request contract in server/service/vpp.go:54-66 has no auto-update fields and therefore never maps them into VPPAppTeam. Either extend the backend POST contract or PATCH the returned version_id; otherwise the new row is created with auto updates off despite the modal selection.

Medium severity Use the correct accessible name for version editing

frontend/​pages/​SoftwarePage/​SoftwareTitleDetailsPage/​LibraryItemAccordion/​LibraryItemAccordion.tsx:395

Enabling this package-specific self-service renderer for version rows gives its button the accessible name “Edit package,” even though it opens Edit version (the new test currently codifies that mismatch). Use “Edit version” when canActivateMultipleVersions is true so screen-reader users hear the correct target.

Medium severity Hide delete action for fallback shim rows

frontend/​pages/​SoftwarePage/​SoftwareTitleDetailsPage/​SoftwareTitleDetailsPage.tsx:473

The fallback row uses SHIM_VERSION_ID = -1, but this callback still opens a delete flow that sends version_id=-1; the file-level invariant explicitly says version mutations must guard id >= 0. Hide the trash action for shim rows so the user cannot submit a guaranteed-invalid delete.

- EditVersionModal: three-way configurationPayload. Unchanged scaffold =
  no change (undefined); empty editor = clear (null for iOS/iPadOS, {}
  for Android, matching the backend's clear values); content = set.
  Widen IEditAppStoreAppFormData.configuration and the inline
  editAppStoreAppVersion param type to accept null.
- Fix overnight auto-update windows shorter than one hour (e.g.
  23:30-00:15) that bypassed the >=60 min check. Extract
  getAutoUpdateWindowDurationMinutes to SoftwarePage/helpers so
  VersionFormFields and EditAutoUpdateConfigModal share one wrap-aware
  implementation. New jest covers same-day, overnight, same-time,
  the 45-min regression case, and malformed inputs.
- versionToFormData stringifies Android configurations before handing
  to the Editor (BE sends json.RawMessage parsed to an object). Widen
  IAppStoreApp.configuration and IAppStoreAppVersion.configuration to
  string | Record<string, unknown>. EditConfigurationModal.getInitialValue
  casts the apple branch to narrow.
// Multi-version delete: route the request by version id and compute
// last-version so the modal can surface the icon/display-name side-effect.
const versionCount = title.app_store_apps?.length ?? 0;
const isVersionDelete = selectedVersionId !== null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I deleted one of two versions on a title that isn't installed anywhere yet. It sent me back to the Software library even though the other version was still there. The Conditions say the row should disappear, so should a version delete stay on the page and refetch?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes thank you! will follow up!

onLabelsClick={() => openEditModalForVersion(version.id)}
onEditClick={() => openEditModalForVersion(version.id)}
onTrashClick={() => openDeleteModalForVersion(version.id)}
onSelfServiceClick={() => openEditModalForVersion(version.id)}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On version rows, the self-service icon still reads "Edit package" but opens Edit version. Should it say "Edit version" like the auto-update icon?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, will follow up!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cdcme cdcme left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking from going to the feature branch, I don't think.

This branch was previously deployed

1 inactive deployment
Docker Hub — e9c22b6e Deployed Oct 7, 2026 by RachelElysia via publish #107954
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App Store app versions: software title Library page, version modals, and host/activity surfaces

4 participants