feat(server): share machines, post settings, clamps and nest defaults

Schema v13 adds shared_settings and shared_setting_revisions behind
/api/settings: one revision-guarded JSON document per kind and key, with
every revision (deletions included) kept with who saved it. Recreating a
deleted document continues its revision count.

OpenNest.Data gains the client (RemoteSharedSettings), a synchronous store
that keeps the last copy read for offline use and refuses writes while the
server is unreachable (SharedSettingsStore), a machine provider over it, and
JSON parse/serialize entry points for clamp settings and nest defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-25 20:02:08 -04:00
1 parent 9f3c22dc4f
commit 90e2fd2e6e
20 files changed
+1266 -60

No files matched your search

+57 -27
View File
@@ -95,26 +95,43 @@ public sealed class NestDefaults
out NestDefaultsStatus status
)
{
var defaults = Fallback;
if (Enum.IsDefined(fallbackUnits))
defaults.Units = fallbackUnits;
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
{
status = NestDefaultsStatus.Missing;
return defaults;
return WithUnits(Fallback, fallbackUnits);
}
NestDefaultsDto? dto;
string json;
try
{
var json = File.ReadAllText(path);
dto = JsonSerializer.Deserialize<NestDefaultsDto>(json, JsonOptions);
json = File.ReadAllText(path);
}
catch (Exception ex) when (IsUnreadableFile(ex))
{
// A file that exists but cannot be read (locked, access-denied)
// or parsed must never block creating a nest.
// must never block creating a nest.
status = NestDefaultsStatus.Invalid;
return WithUnits(Fallback, fallbackUnits);
}
return Parse(json, fallbackUnits, out status);
}
/// <summary>
/// Reads defaults from <paramref name="json"/> (a file or the shared copy) the way
/// <see cref="Load(string, Units, out NestDefaultsStatus)"/> reads a file, never throwing.
/// </summary>
public static NestDefaults Parse(string json, Units fallbackUnits, out NestDefaultsStatus status)
{
var defaults = WithUnits(Fallback, fallbackUnits);
NestDefaultsDto? dto;
try
{
dto = JsonSerializer.Deserialize<NestDefaultsDto>(json ?? "", JsonOptions);
}
catch (Exception ex) when (IsUnreadableFile(ex))
{
// Defaults that cannot be parsed must never block creating a nest.
status = NestDefaultsStatus.Invalid;
return defaults;
}
@@ -162,6 +179,13 @@ public sealed class NestDefaults
return defaults;
}
private static NestDefaults WithUnits(NestDefaults defaults, Units units)
{
if (Enum.IsDefined(units))
defaults.Units = units;
return defaults;
}
/// <summary>
/// Captures the current units and plate defaults from a nest.
/// </summary>
@@ -237,24 +261,7 @@ public sealed class NestDefaults
/// </summary>
public void Save(string path, int maxRetries = 3)
{
var dto = new NestDefaultsDto
{
Version = CurrentVersion,
Units = JsonSerializer.SerializeToElement(Units.ToString().ToLowerInvariant()),
Size = new SizeDto { Width = Size.Width, Length = Size.Length },
Quadrant = Quadrant,
PartSpacing = PartSpacing,
EdgeSpacing = new SpacingDto
{
Left = EdgeSpacing.Left,
Bottom = EdgeSpacing.Bottom,
Right = EdgeSpacing.Right,
Top = EdgeSpacing.Top,
},
MachineId = MachineId is { } machineId ? JsonSerializer.SerializeToElement(machineId.ToString()) : null,
};
var json = JsonSerializer.Serialize(dto, JsonOptions);
var json = ToJson();
var directory = Path.GetDirectoryName(Path.GetFullPath(path));
if (!string.IsNullOrEmpty(directory))
@@ -274,6 +281,29 @@ public sealed class NestDefaults
}
}
/// <summary>The defaults as <see cref="Save"/> writes them, for the shared copy.</summary>
public string ToJson()
{
var dto = new NestDefaultsDto
{
Version = CurrentVersion,
Units = JsonSerializer.SerializeToElement(Units.ToString().ToLowerInvariant()),
Size = new SizeDto { Width = Size.Width, Length = Size.Length },
Quadrant = Quadrant,
PartSpacing = PartSpacing,
EdgeSpacing = new SpacingDto
{
Left = EdgeSpacing.Left,
Bottom = EdgeSpacing.Bottom,
Right = EdgeSpacing.Right,
Top = EdgeSpacing.Top,
},
MachineId = MachineId is { } machineId ? JsonSerializer.SerializeToElement(machineId.ToString()) : null,
};
return JsonSerializer.Serialize(dto, JsonOptions);
}
/// <summary>
/// Accepts only a defined unit name (case-insensitive). Enum.TryParse
/// would also accept numeric strings ("7") and comma-joined names,