merge: add Git mount refresh feedback

This commit is contained in:
2026-07-21 21:18:25 +02:00
7 changed files with 44 additions and 5 deletions
@@ -14,6 +14,7 @@ interface Props {
saveStatus: "idle" | "saving" | "saved" | "error";
previewData: ResolvedProfile | null;
previewingId: string | null;
isRefreshingGitMounts: boolean;
projects: ProjectWithRepos[];
toolTypes: ToolType[];
availableProfiles: ConfigProfile[];
@@ -55,6 +56,7 @@ export const ConfigProfileEditorPanel = ({
saveStatus,
previewData,
previewingId,
isRefreshingGitMounts,
projects,
toolTypes,
availableProfiles,
@@ -116,8 +118,16 @@ export const ConfigProfileEditorPanel = ({
</div>
{!isCreating && selectedProfile && (
<div className="row row-sm">
<button className="btn btn-secondary" onClick={onRefreshGitMounts}>
<Icon name="refresh" size="sm" /> Refresh Git mounts
<button className="btn btn-secondary" onClick={onRefreshGitMounts} disabled={isRefreshingGitMounts}>
{isRefreshingGitMounts ? (
<>
<Icon name="loading" size="sm" /> Refreshing Git mounts...
</>
) : (
<>
<Icon name="refresh" size="sm" /> Refresh Git mounts
</>
)}
</button>
<button className="btn btn-secondary" onClick={onPreview} disabled={previewingId === selectedProfile.id}>
{previewingId === selectedProfile.id ? (
@@ -28,6 +28,7 @@ interface Props {
availableProfiles: ConfigProfile[];
previewData: ResolvedProfile | null;
previewingId: string | null;
isRefreshingGitMounts: boolean;
saveStatus: "idle" | "saving" | "saved" | "error";
error: string | null;
onViewChange: (view: MobileView) => void;
@@ -80,6 +81,7 @@ export const ConfigProfilesMobileView = ({
availableProfiles,
previewData,
previewingId,
isRefreshingGitMounts,
saveStatus,
error,
onViewChange,
@@ -368,9 +370,19 @@ export const ConfigProfilesMobileView = ({
<button
type="button"
className="secondary-button"
disabled={isRefreshingGitMounts}
onClick={() => onRefreshGitMounts(selectedProfile.id)}
>
<Icon name="refresh" size="sm" /> Refresh Git mounts
{isRefreshingGitMounts ? (
<>
<Icon name="loading" size="sm" />
Refreshing Git mounts...
</>
) : (
<>
<Icon name="refresh" size="sm" /> Refresh Git mounts
</>
)}
</button>
<button
type="button"
@@ -43,6 +43,7 @@ export const useConfigProfiles = () => {
const [error, setError] = useState<string | null>(null);
const [previewData, setPreviewData] = useState<ResolvedProfile | null>(null);
const [previewingId, setPreviewingId] = useState<string | null>(null);
const [isRefreshingGitMounts, setIsRefreshingGitMounts] = useState(false);
const [formData, setFormData] =
useState<CreateConfigProfileRequest>(defaultForm);
const [includedProfileIds, setIncludedProfileIds] = useState<string[]>([]);
@@ -275,7 +276,10 @@ export const useConfigProfiles = () => {
};
const handleRefreshGitMounts = async (id: string): Promise<boolean> => {
if (isRefreshingGitMounts) return false;
setError(null);
setIsRefreshingGitMounts(true);
try {
const result = await refreshConfigProfileGitMounts(id);
const refreshed = result.refresh_outcomes.filter(
@@ -290,6 +294,8 @@ export const useConfigProfiles = () => {
} catch (err) {
setError(extractErrorMessage(err));
return false;
} finally {
setIsRefreshingGitMounts(false);
}
};
@@ -445,6 +451,7 @@ export const useConfigProfiles = () => {
error,
previewData,
previewingId,
isRefreshingGitMounts,
formData,
includedProfileIds,
dragOverIndex,
+6 -1
View File
@@ -23,6 +23,7 @@ export const ConfigProfilesPage = () => {
error,
previewData,
previewingId,
isRefreshingGitMounts,
formData,
includedProfileIds,
dragOverIndex,
@@ -111,6 +112,7 @@ export const ConfigProfilesPage = () => {
availableProfiles={availableProfilesForInclude()}
previewData={previewData}
previewingId={previewingId}
isRefreshingGitMounts={isRefreshingGitMounts}
saveStatus={saveStatus}
error={error}
onViewChange={setMobileView}
@@ -169,6 +171,7 @@ export const ConfigProfilesPage = () => {
saveStatus={saveStatus}
previewData={previewData}
previewingId={previewingId}
isRefreshingGitMounts={isRefreshingGitMounts}
projects={projects}
toolTypes={toolTypes}
availableProfiles={availableProfilesForInclude()}
@@ -178,7 +181,9 @@ export const ConfigProfilesPage = () => {
onSubmit={handleSubmit}
onReset={handleReset}
onPreview={() => selectedProfile && handlePreview(selectedProfile.id)}
onRefreshGitMounts={() => selectedProfile && handleRefreshGitMounts(selectedProfile.id)}
onRefreshGitMounts={() =>
selectedProfile && handleRefreshGitMounts(selectedProfile.id)
}
onAddInclude={addInclude}
onRemoveInclude={removeInclude}
onDragStart={handleDragStart}
@@ -14,6 +14,7 @@ Move Git Config Profile mounts to profile-scoped canonical host clones. Bind the
- In-place refresh for existing directory mounts only.
- Explicit outcomes for live refresh, restart-required topology changes, and refresh failures.
- Read-only container Git config mounts.
- An in-progress indicator that prevents duplicate refresh requests in desktop and mobile Config Profile views.
## Out of scope
@@ -21,3 +22,4 @@ Move Git Config Profile mounts to profile-scoped canonical host clones. Bind the
- Global cross-user clone sharing.
- Live mount-topology changes, direct-file mappings, or glob match-set changes.
- Atomic all-files revision switching for processes already reading the mount.
- Automatic refresh of an editor buffer that has already loaded a mounted file.
@@ -17,6 +17,7 @@ Each selected Config Profile owns canonical Git clone directories beneath:
3. Clone into a temporary sibling, then rename on initial creation.
4. For refresh, fetch and update the existing working tree in place.
5. Bind directory mappings read-only. Existing containers see changed directory contents without recreation.
6. When profile and Git mount paths overlap, the instance-local composite source must be synchronized in place during refresh; replacing its root directory would leave a running bind mount attached to the old inode.
## Boundaries
@@ -24,6 +25,7 @@ Each selected Config Profile owns canonical Git clone directories beneath:
- Refresh failure is reported without mutating a known-good checkout.
- No non-Git profile content may be copied into a Git checkout; overlapping targets are rejected or reported.
- Containers must not write to shared Git mount sources.
- A browser editor that already has a file open is not a filesystem watcher; the user must reload that editor buffer after the mounted source changes.
## Security
@@ -4,7 +4,8 @@
- [x] Make Git Config Profile mounts read-only and prevent profile-content copy into Git sources.
- [x] Add lock-protected clone/fetch/ref checkout refresh that preserves a known-good checkout on failure.
- [x] Add save/refresh outcomes for live refresh, restart-required topology, and failures.
- [x] Add desktop/mobile feedback for refresh outcomes.
- [x] Add an in-progress desktop/mobile indicator that disables duplicate Git-mount refresh requests.
- [ ] Synchronize affected instance-local composite mounts in place so live refresh reaches running containers.
- [ ] Add focused resolver/service/API/frontend tests.
- [x] Run available verification and document skipped checks.