Smoke-sweep fixes: exact saves, local dates, safer remove, readable markers
- Garden and plant dialogs keep centimeters as the source of truth (LengthField in lib/units.ts): a no-change Save no longer rewrites 900 cm as 899.922 or a 45 cm spacing as 44.958, bumping versions and writing bogus history entries on the way. - The UI stamps every date with the browser's local day (lib/dates.ts). Journal notes already did; plop placement, fill and removal now do too, so a 9 pm placement isn't "planted tomorrow". The fill endpoint gained an optional plantedAt; API and agent callers still default to UTC today. - Removing an object that holds plants asks first and says how many go with it. An empty one still goes straight away (one Undo restores it). - The expanded plant card's action row wraps instead of clipping "Delete". - Monogram lettering switches to a dark ink on pale marker colors (garlic, cabbage, marigold) instead of near-white on near-white. - Copy-as-plan proposes the next free year and warns when the typed name already exists, so two gardens can't both read as "the 2027 plan". - Plan cards show the base name with a "2027 plan" tag, so the year — the point of the name — survives truncation. - A rejected model spec now says which model and why: a wrapped ErrInvalidInput's reason reaches the client as the 400's message, and the Settings field shows it inline instead of toasting "invalid input". Also defuses a clock bomb in TestRemainingReturnsWhenAPlantingIsRemoved, which only passed while the real date was before 2026-08-01. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
@@ -2,6 +2,8 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.stevedudenhoeffer.com/steve/pansy/internal/agentmodel"
|
||||
@@ -61,12 +63,15 @@ func (s *Service) UpdateInstanceSettings(ctx context.Context, actorID int64, pat
|
||||
model := strings.TrimSpace(patch.AgentModel)
|
||||
vision := strings.TrimSpace(patch.VisionModel)
|
||||
// Validate non-empty specs up front. An empty one is the "inherit env"
|
||||
// sentinel and needs no check — the env value was validated at boot.
|
||||
for _, spec := range []string{model, vision} {
|
||||
if spec != "" {
|
||||
if err := agentmodel.Validate(s.cfg.Agent.OllamaCloudAPIKey, spec); err != nil {
|
||||
return nil, domain.ErrInvalidInput
|
||||
}
|
||||
// sentinel and needs no check — the env value was validated at boot. The
|
||||
// reason rides on the sentinel so the 400 can show it: "unknown provider"
|
||||
// is something a person can act on, "invalid input" is not.
|
||||
for _, f := range []struct{ label, spec string }{{"chat model", model}, {"vision model", vision}} {
|
||||
if f.spec == "" {
|
||||
continue
|
||||
}
|
||||
if err := agentmodel.Validate(s.cfg.Agent.OllamaCloudAPIKey, f.spec); err != nil {
|
||||
return nil, fmt.Errorf("%w: %s %q: %v", domain.ErrInvalidInput, f.label, f.spec, specReason(err))
|
||||
}
|
||||
}
|
||||
return s.store.UpdateInstanceSettings(ctx, &domain.InstanceSettings{
|
||||
@@ -166,3 +171,12 @@ func (s *Service) EffectiveConfig(ctx context.Context) (EffectiveAgent, Effectiv
|
||||
}
|
||||
return s.agentOver(st), s.visionOver(st), nil
|
||||
}
|
||||
|
||||
// specReason strips agentmodel's own "resolve %q:" wrapping so the message
|
||||
// reads "unknown provider …" rather than repeating the spec twice.
|
||||
func specReason(err error) string {
|
||||
if u := errors.Unwrap(err); u != nil {
|
||||
return u.Error()
|
||||
}
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user