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:
@@ -1,7 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -159,10 +161,22 @@ func TestSettingsRejectsBadModel(t *testing.T) {
|
||||
admin := registerAndCookie(t, r, "[email protected]")
|
||||
v := settingsVersion(t, r, admin)
|
||||
|
||||
if w := doJSON(t, r, http.MethodPatch, "/api/v1/settings",
|
||||
map[string]any{"agentModel": "nonesuch/model", "version": v}, admin); w.Code != http.StatusBadRequest {
|
||||
w := doJSON(t, r, http.MethodPatch, "/api/v1/settings",
|
||||
map[string]any{"agentModel": "nonesuch/model", "version": v}, admin)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("bad model: status %d, want 400", w.Code)
|
||||
}
|
||||
// The message says which field and which spec, so the page can show a reason
|
||||
// rather than a bare "invalid input".
|
||||
var body struct {
|
||||
Error struct{ Code, Message string } `json:"error"`
|
||||
}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("decode bad-model body: %v", err)
|
||||
}
|
||||
if body.Error.Code != "INVALID_INPUT" || !strings.Contains(body.Error.Message, "chat model") || !strings.Contains(body.Error.Message, "nonesuch/model") {
|
||||
t.Errorf("bad model error = %+v, want INVALID_INPUT naming the chat model and spec", body.Error)
|
||||
}
|
||||
// agentEnabled must be a bool or null, not a string.
|
||||
if w := doJSON(t, r, http.MethodPatch, "/api/v1/settings",
|
||||
map[string]any{"agentModel": "", "agentEnabled": "yes", "version": v}, admin); w.Code != http.StatusBadRequest {
|
||||
|
||||
Reference in New Issue
Block a user