Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
23c3395
refactor: describe configuration and action forms with a dedicated UI…
revam Aug 22, 2026
1d9b50e
refactor(abstractions): group form members with a structure and float…
revam Sep 17, 2026
cefd73f
fix(config): unwrap and trim the descriptions taken from XML docs
revam Sep 17, 2026
2aba359
refactor(abstractions): rename `UiAction.ID` to `Name`
revam Sep 17, 2026
4222b70
fix(config): run the lifecycle hook that was asked for
revam Sep 17, 2026
8302b09
feat(abstractions): say which branches of a form react to live edits
revam Sep 17, 2026
4aa1060
feat(abstractions): render an action on the row of the member it names
revam Sep 17, 2026
7e8187b
chore(config): catch the UI definition up with master's settings
revam Sep 17, 2026
01803b6
chore: sort usings
revam Sep 17, 2026
8b9886e
docs(abstractions): document how a form is laid out and reacts
revam Sep 17, 2026
93f15ef
feat(abstractions): describe a gathered section, and read `[DataType]`
revam Sep 17, 2026
89474e6
docs(abstractions): say what a `Load` or `Save` handler is handed
revam Sep 17, 2026
b0fce4f
feat(abstractions): compare a condition with an operator
revam Sep 17, 2026
f9a8319
feat(abstractions): report an unusable condition at the authoring site
revam Sep 18, 2026
dd3b709
feat(abstractions): let a live-edit handler name the members it watches
revam Sep 18, 2026
f4a6156
refactor(abstractions): let a live-edit handler say what it reacts to
revam Sep 18, 2026
12f0dc4
feat(abstractions): report a handler that cannot react to what it names
revam Sep 18, 2026
11f8883
refactor(abstractions): case a select's value like the rest of the do…
revam Sep 19, 2026
faebe9d
chore: sort usings
revam Sep 19, 2026
248b422
fix(config): file denied values on the element that can act on them
revam Sep 19, 2026
53d52c1
feat(config): run every live-edit handler the edited path passes through
revam Sep 19, 2026
9b1bfda
feat(abstractions): let a row say what it is called, and settle a col…
revam Sep 19, 2026
cfe4c77
chore(config): catch the UI definition up with master's action and se…
revam Sep 20, 2026
0ce2625
refactor(abstractions): let a plugin validate an action payload too
revam Sep 20, 2026
60ed222
chore(tests): regenerate the `ServerSettings` schema golden
revam Sep 22, 2026
70917c7
chore(config): catch the UI definition up with master's metadata and …
revam Oct 1, 2026
69095b3
chore(config): catch the UI definition up with master's TMDb plugin a…
revam Oct 3, 2026
9865e1b
fix(config): reconcile the rebased UI definition with master's metada…
revam Oct 7, 2026
d9ca97a
feat(abstractions): let a member take options the server lists
revam Oct 8, 2026
b6db467
feat(abstractions): report an options provider that does not fit its …
revam Oct 8, 2026
93f6949
refactor(abstractions): drop actions attached to a member's row
revam Oct 8, 2026
f39ecce
feat(abstractions): hand an action's options provider the action as e…
revam Oct 8, 2026
3826a9e
refactor(abstractions): declare an options provider on its method, fo…
revam Oct 8, 2026
85a80f0
fix(abstractions): stop SHOKO0007 throwing on a handler naming only e…
revam Oct 8, 2026
9c6cdf0
feat(abstractions): list options for dictionary keys and values, and …
revam Oct 8, 2026
d29696e
docs: document the analyzer rules and the options contract
revam Oct 8, 2026
9ffa594
fix(config): describe a type parsable from text as text
revam Oct 8, 2026
2aecf78
docs: show stored values by their option labels
revam Oct 8, 2026
70fb918
feat(abstractions): label enum options the way the form names them
revam Oct 8, 2026
d813143
feat(abstractions): treat a flag enum as a list of its members
revam Oct 8, 2026
7a0db1e
refactor(abstractions): make `OptionsRoute` relative to the form's route
revam Oct 9, 2026
e7f03ec
refactor(abstractions): pick options by path and replace `OptionsRout…
revam Oct 9, 2026
1f28225
feat: write parsable types as text in configurations and actions
revam Oct 9, 2026
fa8ff18
fix: report unreadable action parameters as validation errors
revam Oct 9, 2026
2afdf54
fix(api): write `ReactiveEventType` by name and bind it on `LiveEdit`
revam Oct 9, 2026
ec34469
fix(config): run the `Validate` hook before every save
revam Oct 9, 2026
a9f85be
fix(config): take UI defaults from a constructed instance
revam Oct 9, 2026
e0c005d
fix(actions): read options drafts leniently and respect required para…
revam Oct 9, 2026
a6df189
feat(abstractions): name the switch of a checkbox section
revam Oct 9, 2026
83f77a4
feat(config): list plugin options for `EnabledPlugins` and `Priority`
revam Oct 11, 2026
91825a9
refactor(config): test the UI definition builder on a fixture, not th…
revam Oct 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion Shoko.Abstractions/Actions/ExecutableActionInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Shoko.Abstractions.UI;

namespace Shoko.Abstractions.Actions;

Expand Down Expand Up @@ -44,6 +45,27 @@ namespace Shoko.Abstractions.Actions;
/// <param name="PluginId">
/// The ID of the plugin that owns the action.
/// </param>
/// <param name="ParameterDefinition">
/// A render-ready description of the action's invocation parameters, or
/// <c>null</c> when the action declares none.
/// </param>
/// <remarks>
/// <para>
/// An action's parameters are simply its own settable, serialized
/// properties, since the caller's payload is populated straight onto the
/// action instance, so they are described by the very same
/// <see cref="UiDefinition"/> a configuration is described by. A client
/// renders an invocation form from it exactly as it renders a
/// configuration editor.
/// </para>
/// <para>
/// The action's own metadata surface (<see cref="Name"/>,
/// <see cref="Description"/>, <see cref="Category"/>,
/// <see cref="Permission"/>, <see cref="RequiresConfirmation"/>,
/// <see cref="Scope"/> and the scoped context) is deliberately not part of
/// it; those are described by this record instead.
/// </para>
/// </remarks>
public sealed record ExecutableActionInfo(
Guid ID,
string Name,
Expand All @@ -55,5 +77,6 @@ public sealed record ExecutableActionInfo(
ActionPermission Permission,
bool RequiresConfirmation,
string? ConfirmationMessage,
Guid PluginId
Guid PluginId,
UiDefinition? ParameterDefinition
);
263 changes: 262 additions & 1 deletion Shoko.Abstractions/Actions/Services/IActionService.cs

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions Shoko.Abstractions/Actions/Services/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,11 @@ name with no matching property is ignored silently, typos included. Both the
validation probe and the executing instance are populated, so `Validate` sees
what `Execute` will.

An omitted parameter keeps the value the instance was constructed with, so a
parameter is optional unless the action marks it `[Required]` or declares it
`required`. Such a parameter is marked required in the action's UI definition,
and `ValidateParameters` refuses a body without it, an absent body included.

### Asking without running

`ValidateAsync` runs everything `InvokeAsync` does before it queues (scope,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,40 @@ public class ConfigurationActionAttribute(ConfigurationActionType actionType) :
public ConfigurationActionType ActionType { get; set; } = actionType;

/// <summary>
/// The reactive event type this action handles.
/// The reactive events this action handles. Leave it unset to handle
/// every event.
/// </summary>
public ReactiveEventType ReactiveEventType { get; set; } = ReactiveEventType.All;
/// <remarks>
/// A request carries one event, while a handler is interested in as many
/// as it likes, so this is the plural side of the same enum. A handler
/// that names the raised event is preferred over one that named none.
/// Only <see cref="ConfigurationActionType.LiveEdit"/> is raised by an
/// event, so this says nothing on any other hook.
/// </remarks>
public ReactiveEventType[]? Events { get; set; }

/// <summary>
/// The members this action watches. Leave it unset to watch everything in
/// the class it is declared on, and everything below it that has no
/// handler of its own.
/// </summary>
/// <remarks>
/// <para>
/// Naming the members is what lets a client tell whether an edit is
/// worth sending at all: it reaches the element as
/// <c>ReactsToLiveEdit</c>, and an edit to anything else is never sent.
/// </para>
/// <para>
/// A name may descend into a nested class, spelled
/// <c>$"{nameof(Nested)}.{nameof(NestedConfiguration.Member)}"</c>,
/// which stays a constant and survives a rename. It cannot point
/// through a list or a dictionary, which carries no index to say which
/// entry it meant.
/// </para>
/// <para>
/// Only <see cref="ConfigurationActionType.LiveEdit"/> is raised by an
/// edit, so this says nothing on any other hook.
/// </para>
/// </remarks>
public string[]? ReactiveMembers { get; set; }
}
125 changes: 0 additions & 125 deletions Shoko.Abstractions/Config/Attributes/CustomActionAttribute.cs

This file was deleted.

13 changes: 13 additions & 0 deletions Shoko.Abstractions/Config/ConfigurationActionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ public class ConfigurationActionContext
/// </summary>
public required ConfigurationInfo Info { get; init; }

/// <summary>
/// What the handler before this one in the chain returned, or an empty
/// result for the first of them.
/// </summary>
/// <remarks>
/// Every handler along the edited path runs, innermost first, and each is
/// handed what the one before it decided so it can build on it. Returning
/// something else loses nothing: the messages and validation errors of every
/// handler are collected whatever each one returns, and they all edit the
/// same configuration instance.
/// </remarks>
public ConfigurationActionResult PreviousResult { get; init; } = new();

/// <summary>
/// The configuration service.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion Shoko.Abstractions/Config/ConfigurationActionMessage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Shoko.Abstractions.Config.Enums;
using Shoko.Abstractions.UI.Enums;

namespace Shoko.Abstractions.Config;

Expand Down
2 changes: 1 addition & 1 deletion Shoko.Abstractions/Config/ConfigurationActionResult.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.Collections.Generic;
using Shoko.Abstractions.Config.Enums;
using Shoko.Abstractions.UI.Enums;

namespace Shoko.Abstractions.Config;

Expand Down
27 changes: 27 additions & 0 deletions Shoko.Abstractions/Config/ConfigurationInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using NJsonSchema;
using Shoko.Abstractions.Config.Services;
using Shoko.Abstractions.Plugin.Models;
using Shoko.Abstractions.UI;

namespace Shoko.Abstractions.Config;

Expand Down Expand Up @@ -114,6 +115,32 @@ public IReadOnlySet<string> LoadedEnvironmentVariables
/// </summary>
public required JsonSchema Schema { get; init; }

private UiDefinition? _uiDefinition = null;

/// <summary>
/// A render-ready description of how to lay out an editor for the
/// configuration, in the same shape an executable action's parameters are
/// described by.
/// </summary>
/// <remarks>
/// <para>
/// Unlike <see cref="Schema"/>, the returned document is meant to be
/// sufficient on its own: every element carries a concrete element kind,
/// its label, its default and the constraints needed for a cheap
/// client-side pre-check. The schema remains the authority for server-side
/// validation.
/// </para>
/// <para>
/// Built on first read and held from then on. A configuration's definition
/// can run to a hundred kilobytes or more, so nothing pays for one until it
/// asks for it, and nothing pays twice. Two threads racing the first read
/// both build one and the later assignment wins, which costs a discarded
/// tree and never a wrong answer.
/// </para>
/// </remarks>
public UiDefinition UiDefinition
=> _uiDefinition ??= _configurationService.GenerateUiDefinition(Type);

/// <summary>
/// Information about the plugin that the configuration belongs to.
/// </summary>
Expand Down
64 changes: 0 additions & 64 deletions Shoko.Abstractions/Config/Enums/CodeEditorLanguage.cs

This file was deleted.

Loading
Loading