feat: add code diff display mode preference (#25027)

This commit is contained in:
Danielle Maywood
2026-05-07 20:15:28 +01:00
committed by GitHub
parent d32842f084
commit e7958713a9
29 changed files with 968 additions and 207 deletions
+120 -51
View File
@@ -1248,9 +1248,19 @@ func (api *API) userPreferenceSettings(rw http.ResponseWriter, r *http.Request)
return
}
codeDiffMode, err := api.Database.GetUserCodeDiffDisplayMode(ctx, user.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Error reading user preference settings.",
Detail: err.Error(),
})
return
}
httpapi.Write(ctx, rw, http.StatusOK, codersdk.UserPreferenceSettings{
TaskNotificationAlertDismissed: taskAlertDismissed,
ThinkingDisplayMode: sanitizeThinkingDisplayMode(thinkingMode),
CodeDiffDisplayMode: sanitizeAgentDisplayMode(codeDiffMode),
})
}
@@ -1280,69 +1290,120 @@ func (api *API) putUserPreferenceSettings(rw http.ResponseWriter, r *http.Reques
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid thinking display mode.",
Validations: []codersdk.ValidationError{
{Field: "thinking_display_mode", Detail: "must be one of: auto, preview, always_expanded, always_collapsed"},
{Field: "thinking_display_mode", Detail: thinkingDisplayModeValidationDetail},
},
})
return
}
var err error
var updatedTaskAlertDismissed bool
if params.TaskNotificationAlertDismissed != nil {
updatedTaskAlertDismissed, err = api.Database.UpdateUserTaskNotificationAlertDismissed(ctx, database.UpdateUserTaskNotificationAlertDismissedParams{
UserID: user.ID,
TaskNotificationAlertDismissed: *params.TaskNotificationAlertDismissed,
if params.CodeDiffDisplayMode != "" &&
!slices.Contains(codersdk.ValidAgentDisplayModes, params.CodeDiffDisplayMode) {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid code diff display mode.",
Validations: []codersdk.ValidationError{
{Field: "code_diff_display_mode", Detail: agentDisplayModeValidationDetail},
},
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error updating user task notification alert dismissed.",
Detail: err.Error(),
})
return
}
} else {
updatedTaskAlertDismissed, err = api.Database.GetUserTaskNotificationAlertDismissed(ctx, user.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Error reading task notification alert dismissed.",
Detail: err.Error(),
return
}
var settings codersdk.UserPreferenceSettings
err := api.Database.InTx(func(tx database.Store) error {
var err error
if params.TaskNotificationAlertDismissed != nil {
settings.TaskNotificationAlertDismissed, err = tx.UpdateUserTaskNotificationAlertDismissed(ctx, database.UpdateUserTaskNotificationAlertDismissedParams{
UserID: user.ID,
TaskNotificationAlertDismissed: *params.TaskNotificationAlertDismissed,
})
if err != nil {
return newUserPreferenceSettingsAPIError("Internal error updating user task notification alert dismissed.", err)
}
} else {
settings.TaskNotificationAlertDismissed, err = tx.GetUserTaskNotificationAlertDismissed(ctx, user.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return newUserPreferenceSettingsAPIError("Error reading task notification alert dismissed.", err)
}
}
if params.ThinkingDisplayMode != "" {
updated, err := tx.UpdateUserThinkingDisplayMode(ctx, database.UpdateUserThinkingDisplayModeParams{
UserID: user.ID,
ThinkingDisplayMode: string(params.ThinkingDisplayMode),
})
if err != nil {
return newUserPreferenceSettingsAPIError("Internal error updating thinking display mode.", err)
}
settings.ThinkingDisplayMode = sanitizeThinkingDisplayMode(updated)
} else {
stored, err := tx.GetUserThinkingDisplayMode(ctx, user.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return newUserPreferenceSettingsAPIError("Error reading thinking display mode.", err)
}
settings.ThinkingDisplayMode = sanitizeThinkingDisplayMode(stored)
}
if params.CodeDiffDisplayMode != "" {
updated, err := tx.UpdateUserCodeDiffDisplayMode(ctx, database.UpdateUserCodeDiffDisplayModeParams{
UserID: user.ID,
CodeDiffDisplayMode: string(params.CodeDiffDisplayMode),
})
if err != nil {
return newUserPreferenceSettingsAPIError("Internal error updating code diff display mode.", err)
}
settings.CodeDiffDisplayMode = sanitizeAgentDisplayMode(updated)
} else {
stored, err := tx.GetUserCodeDiffDisplayMode(ctx, user.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return newUserPreferenceSettingsAPIError("Error reading code diff display mode.", err)
}
settings.CodeDiffDisplayMode = sanitizeAgentDisplayMode(stored)
}
return nil
}, database.DefaultTXOptions().WithID("user_preference_settings"))
if err != nil {
var apiErr userPreferenceSettingsAPIError
if errors.As(err, &apiErr) {
httpapi.Write(ctx, rw, apiErr.statusCode, apiErr.response)
return
}
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error updating user preference settings.",
Detail: err.Error(),
})
return
}
var resolvedThinkingMode codersdk.ThinkingDisplayMode
if params.ThinkingDisplayMode != "" {
updated, err := api.Database.UpdateUserThinkingDisplayMode(ctx, database.UpdateUserThinkingDisplayModeParams{
UserID: user.ID,
ThinkingDisplayMode: string(params.ThinkingDisplayMode),
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error updating thinking display mode.",
Detail: err.Error(),
})
return
}
resolvedThinkingMode = codersdk.ThinkingDisplayMode(updated)
} else {
stored, err := api.Database.GetUserThinkingDisplayMode(ctx, user.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Error reading thinking display mode.",
Detail: err.Error(),
})
return
}
resolvedThinkingMode = sanitizeThinkingDisplayMode(stored)
}
httpapi.Write(ctx, rw, http.StatusOK, codersdk.UserPreferenceSettings{
TaskNotificationAlertDismissed: updatedTaskAlertDismissed,
ThinkingDisplayMode: resolvedThinkingMode,
})
httpapi.Write(ctx, rw, http.StatusOK, settings)
}
type userPreferenceSettingsAPIError struct {
statusCode int
response codersdk.Response
err error
}
func newUserPreferenceSettingsAPIError(message string, err error) userPreferenceSettingsAPIError {
return userPreferenceSettingsAPIError{
statusCode: http.StatusInternalServerError,
response: codersdk.Response{
Message: message,
Detail: err.Error(),
},
err: err,
}
}
func (e userPreferenceSettingsAPIError) Error() string {
return fmt.Sprintf("%s: %s", e.response.Message, e.err)
}
func (e userPreferenceSettingsAPIError) Unwrap() error {
return e.err
}
const (
thinkingDisplayModeValidationDetail = "must be one of: auto, preview, always_expanded, always_collapsed"
agentDisplayModeValidationDetail = "must be one of: auto, always_expanded, always_collapsed"
)
func sanitizeThinkingDisplayMode(raw string) codersdk.ThinkingDisplayMode {
mode := codersdk.ThinkingDisplayMode(raw)
if slices.Contains(codersdk.ValidThinkingDisplayModes, mode) {
@@ -1351,6 +1412,14 @@ func sanitizeThinkingDisplayMode(raw string) codersdk.ThinkingDisplayMode {
return codersdk.ThinkingDisplayModeAuto
}
func sanitizeAgentDisplayMode(raw string) codersdk.AgentDisplayMode {
mode := codersdk.AgentDisplayMode(raw)
if slices.Contains(codersdk.ValidAgentDisplayModes, mode) {
return mode
}
return codersdk.AgentDisplayModeAuto
}
func isValidFontName(font codersdk.TerminalFontName) bool {
return slices.Contains(codersdk.TerminalFontNames, font)
}