Configuration system #49

Merged
cswimr merged 56 commits from feat/config/init into main 2025-12-08 17:15:23 -05:00
Owner

I would like to write a thin, type-safe (to the best of my ability) wrapper around Red-DiscordBot's Config framework.

Red's Config framework is awesome, and works well enough as is. It's really easy to use and understand, and that's a good thing. You don't have the overhead of learning something like Piccolo or (shiver) SQLAlchemy just to store some data, just toss it into Config!

However, Config does have quite a few quirks that make it less easy to use, often in ways that are not immediately obvious to newer cog developers. Here are some of what I would consider pressing issues with Red's existing Config framework.

  • No type safety or data validation. You have no idea what is actually being returned from Config unless you manually check its type. Blowing up on unexpected data is fine, but this should be controlled. Letting unexpected data get down the line into your actual code is dangerous, and can cause issues that are nigh impossible to reproduce or debug. Instead, the configuration framework should blow up on a validation step outside of the actual cog's logic, so that there's no risk of unintended behavior due to an improper type being passed.
    • Validate the type of configuration values using Pydantic, at both set-time and get-time. Also allows for constraints where possible, so regex matching for strings, gt/lt for integers, etc.
    • Allow using Pydantic models as a type, so nested data structures can exist.
  • No static analysis support. Config uses attributes that don't actually exist on the Config class prior to runtime (as far as I understand, not super familiar with the internals). This means that your IDE / type-checker / linting tool has no way of knowing if an attribute you're providing actually exists or not. force_registration=True is technically a sort-of workaround to issues this causes at runtime, but this does nothing for tooling that utilizes static analysis.
    • Ensure everything a developer expects to exist does so statically, so as to allow for static analysis and IDE integration.
  • Unscalable configuration UX for end-users, or what I like to call "configuration command sprawl". I've noticed this a lot in my own cogs, and even done some (albeit ugly) work to "solve" it. This is when a cog has too many configuration commands, and makes it difficult for a new user to get up to speed with the cog's featureset.
    • Offer a configuration menu, likely utilizing Discord's Components V2 system.

Just as an example, this is what a cog utilizing this module might look like today. I would read this as if it were psuedo-code. Note that this is liable to change over time, and none of this module's structure is finalized. (In fact, as of writing, it doesn't actually work yet... pydantic issues... sigh)

from typing import Annotated, ClassVar, reveal_type

from pydantic import PositiveInt
from redbot.core import commands
from tidegear import BaseConfigSchema, Cog, ConfigMeta, GlobalConfigOption, GuildConfigOption
from typing_extensions import override


class ExampleConfigSchema(BaseConfigSchema):
    version: ClassVar[PositiveInt] = 1

    example_global_option: Annotated[GlobalConfigOption[str], ConfigMeta(default="example string")]
    example_guild_option: Annotated[GuildConfigOption[int], ConfigMeta(default=100)]


class ExampleCog(Cog):
    config: ExampleConfigSchema
    _config_identifier: int = 1234567890  # Consider using your Discord ID for this, instead of a random assortment of numbers.

    @override
    async def cog_load(self) -> None:
        await super().cog_load()  # This is important! Tidegear has its own logic within the `cog_load` method that expects to be run.
        self.config = await ExampleConfigSchema.init(cog_name=self.metadata.name, identifier=self._config_identifier, logger=self.logger)

    @commands.is_owner()
    @commands.command()
    async def global_config(self, ctx: commands.Context, *, value: str | None = None) -> None:
        if value:
            # Works
            await self.config.example_global_option.set(value)
            # ValidationError: Incorrect type, expected `int` (got `list[int]`)
            await self.config.example_global_option.set([value])

            await ctx.send(f"Set `{self.config.example_global_option.key}` to `{value}`")
        else:
            current_value = await self.config.example_global_option.get()
            # OR
            current_value = await self.config.example_global_option()
            # Both of these lines work and do the same thing.
            # The `__call__` interface is provided to be more familiar to Red Config users.

            reveal_type(current_value)  # str
            await ctx.send(f"The current value of the `{self.config.example_global_option.key}` setting is `{current_value}`.")

    @commands.admin()
    @commands.guild_only()
    @commands.command()
    async def guild_config(self, ctx: commands.GuildContext, *, value: int | None = None) -> None:
        if value:
            await self.config.example_guild_option.set(ctx.guild, value)
            await ctx.send(f"Set `{self.config.example_guild_option.key}` to `{value}`")
        else:
            current_value = await self.config.example_guild_option.get(ctx.guild)
            reveal_type(current_value)  # int
            await ctx.send(f"The current value of the `{self.config.example_guild_option.key}` setting is `{current_value}`.")
I would like to write a thin, type-safe (to the best of my ability) wrapper around Red-DiscordBot's [`Config`](https://docs.discord.red/en/stable/framework_config.html) framework. Red's Config framework is awesome, and works well enough as is. It's really easy to use and understand, and that's a good thing. You don't have the overhead of learning something like Piccolo or *(shiver)* SQLAlchemy just to store some data, just toss it into Config! However, Config does have quite a few quirks that make it less easy to use, often in ways that are not immediately obvious to newer cog developers. Here are some of what I would consider pressing issues with Red's existing Config framework. - **No type safety or data validation.** You have no idea what is *actually* being returned from Config unless you manually check its type. Blowing up on unexpected data is fine, but this should be controlled. Letting unexpected data get down the line into your actual code is dangerous, and can cause issues that are nigh impossible to reproduce or debug. Instead, the configuration framework should blow up on a validation step outside of the actual cog's logic, so that there's no risk of unintended behavior due to an improper type being passed. - [x] Validate the type of configuration values using [Pydantic](https://pydantic.dev/), at both set-time and get-time. Also allows for constraints where possible, so regex matching for strings, gt/lt for integers, etc. - [x] Allow using [Pydantic models](https://tidegear.csw.im/stable/ref/pydantic/#tidegear.pydantic.BaseModel) as a type, so nested data structures can exist. - **No static analysis support.** Config uses attributes that don't actually exist on the Config class prior to runtime (as far as I understand, not super familiar with the internals). This means that your IDE / type-checker / linting tool has no way of knowing if an attribute you're providing actually exists or not. [`force_registration=True`](https://docs.discord.red/en/stable/framework_config.html#tutorial) is technically a sort-of workaround to issues this causes at runtime, but this does nothing for tooling that utilizes static analysis. - [x] Ensure everything a developer expects to exist does so statically, so as to allow for static analysis and IDE integration. - **Unscalable configuration UX for end-users**, or what I like to call *"configuration command sprawl"*. I've noticed this a lot in my own cogs, and even done some (albeit ugly) work to "solve" it. This is when a cog has too many configuration commands, and makes it difficult for a new user to get up to speed with the cog's featureset. - [ ] Offer a configuration menu, likely utilizing Discord's Components V2 system. Just as an example, this is what a cog utilizing this module might look like today. **I would read this as if it were psuedo-code.** Note that this is liable to change over time, and none of this module's structure is finalized. (In fact, as of writing, it doesn't actually *work* yet... pydantic issues... *sigh*) ```py from typing import Annotated, ClassVar, reveal_type from pydantic import PositiveInt from redbot.core import commands from tidegear import BaseConfigSchema, Cog, ConfigMeta, GlobalConfigOption, GuildConfigOption from typing_extensions import override class ExampleConfigSchema(BaseConfigSchema): version: ClassVar[PositiveInt] = 1 example_global_option: Annotated[GlobalConfigOption[str], ConfigMeta(default="example string")] example_guild_option: Annotated[GuildConfigOption[int], ConfigMeta(default=100)] class ExampleCog(Cog): config: ExampleConfigSchema _config_identifier: int = 1234567890 # Consider using your Discord ID for this, instead of a random assortment of numbers. @override async def cog_load(self) -> None: await super().cog_load() # This is important! Tidegear has its own logic within the `cog_load` method that expects to be run. self.config = await ExampleConfigSchema.init(cog_name=self.metadata.name, identifier=self._config_identifier, logger=self.logger) @commands.is_owner() @commands.command() async def global_config(self, ctx: commands.Context, *, value: str | None = None) -> None: if value: # Works await self.config.example_global_option.set(value) # ValidationError: Incorrect type, expected `int` (got `list[int]`) await self.config.example_global_option.set([value]) await ctx.send(f"Set `{self.config.example_global_option.key}` to `{value}`") else: current_value = await self.config.example_global_option.get() # OR current_value = await self.config.example_global_option() # Both of these lines work and do the same thing. # The `__call__` interface is provided to be more familiar to Red Config users. reveal_type(current_value) # str await ctx.send(f"The current value of the `{self.config.example_global_option.key}` setting is `{current_value}`.") @commands.admin() @commands.guild_only() @commands.command() async def guild_config(self, ctx: commands.GuildContext, *, value: int | None = None) -> None: if value: await self.config.example_guild_option.set(ctx.guild, value) await ctx.send(f"Set `{self.config.example_guild_option.key}` to `{value}`") else: current_value = await self.config.example_guild_option.get(ctx.guild) reveal_type(current_value) # int await ctx.send(f"The current value of the `{self.config.example_guild_option.key}` setting is `{current_value}`.") ```
cswimr self-assigned this 2025-11-10 15:29:51 -05:00
init config module
Some checks failed
Actions / Build (pull_request) Successful in 35s
Actions / Lint (pull_request) Failing after 34s
Actions / Run Tests (pull_request) Successful in 37s
Actions / Build Documentation (pull_request) Failing after 41s
65bf9962b0
doesn't work yet but I want to get something committed
add another paragraph to the module docstring
Some checks failed
Actions / Lint (pull_request) Failing after 23s
Actions / Build (pull_request) Successful in 35s
Actions / Run Tests (pull_request) Successful in 36s
Actions / Build Documentation (pull_request) Failing after 37s
12abd4cb7f
idiomatic -> declarative
Some checks failed
Actions / Build (pull_request) Successful in 13s
Actions / Lint (pull_request) Failing after 34s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Build Documentation (pull_request) Failing after 37s
64209aaab0
only support pydantic BaseModel in Jsonable
Some checks failed
Actions / Run Tests (pull_request) Failing after 23s
Actions / Build (pull_request) Successful in 33s
Actions / Lint (pull_request) Failing after 34s
Actions / Build Documentation (pull_request) Failing after 37s
4c935b8b57
should help to make the code a bit more understandable, especially within Sentinel where you also have PartialUser/Guild/Role/Channel confusing things.
as of tidegear 1.19.0, tidegear depends on pydantic directly. before that, it was an optional dependency, which was why this docstring recommended the use of `yarl`.
Co-authored-by: Renovate <renovate@csw.im>
Co-committed-by: Renovate <renovate@csw.im>
Lock file maintenance (#51)
Some checks failed
Actions / Lint (pull_request) Failing after 22s
Actions / Run Tests (pull_request) Failing after 25s
Actions / Build (pull_request) Successful in 34s
Actions / Build Documentation (pull_request) Failing after 35s
13a6412466
Co-authored-by: Renovate <renovate@csw.im>
Co-committed-by: Renovate <renovate@csw.im>
cswimr force-pushed feat/config/init from 13a6412466
Some checks failed
Actions / Lint (pull_request) Failing after 22s
Actions / Run Tests (pull_request) Failing after 25s
Actions / Build (pull_request) Successful in 34s
Actions / Build Documentation (pull_request) Failing after 35s
to 4c935b8b57
Some checks failed
Actions / Run Tests (pull_request) Failing after 23s
Actions / Build (pull_request) Successful in 33s
Actions / Lint (pull_request) Failing after 34s
Actions / Build Documentation (pull_request) Failing after 37s
2025-11-24 07:31:30 -05:00
Compare
Merge branch 'main' into feat/config/init
Some checks failed
Actions / Run Tests (pull_request) Failing after 25s
Actions / Build (pull_request) Successful in 33s
Actions / Lint (pull_request) Failing after 34s
Actions / Build Documentation (pull_request) Failing after 37s
7edc15eeb1
fix tests not passing
Some checks failed
Actions / Lint (pull_request) Failing after 22s
Actions / Build (pull_request) Successful in 33s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Build Documentation (pull_request) Failing after 36s
9ce38a70c7
move Jsonable to tidegear.config and redefine it; work on serialization
Some checks failed
Actions / Build (pull_request) Successful in 33s
Actions / Lint (pull_request) Failing after 32s
Actions / Run Tests (pull_request) Successful in 37s
Actions / Build Documentation (pull_request) Failing after 37s
429cc2be9c
Merge branch 'main' into feat/config/init
Some checks failed
Actions / Lint (pull_request) Failing after 23s
Actions / Build (pull_request) Successful in 34s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Build Documentation (pull_request) Failing after 37s
7aa8058da0
correct error message
Some checks failed
Actions / Build (pull_request) Successful in 12s
Actions / Lint (pull_request) Failing after 32s
Actions / Run Tests (pull_request) Successful in 34s
Actions / Build Documentation (pull_request) Failing after 36s
5a836c6a2d
add copyright header
Some checks failed
Actions / Build (pull_request) Successful in 34s
Actions / Lint (pull_request) Failing after 33s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Build Documentation (pull_request) Failing after 36s
9178142819
don't make ConfigSchema a pydantic class
Some checks failed
Actions / Lint (pull_request) Failing after 32s
Actions / Build (pull_request) Successful in 34s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Build Documentation (pull_request) Failing after 35s
9ce928530d
add missing docstring
Some checks failed
Actions / Lint (pull_request) Failing after 22s
Actions / Build (pull_request) Successful in 33s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Build Documentation (pull_request) Failing after 38s
7de3ff7db2
Merge branch 'main' into feat/config/init
Some checks failed
Actions / Build (pull_request) Successful in 13s
Actions / Lint (pull_request) Failing after 33s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Build Documentation (pull_request) Failing after 37s
29b0e0f486
support normal pydantic basemodels in config instead of tidegear basemodels
Some checks failed
Actions / Run Tests (pull_request) Successful in 13s
Actions / Lint (pull_request) Failing after 33s
Actions / Build (pull_request) Successful in 34s
Actions / Build Documentation (pull_request) Failing after 36s
ee8756899c
*actually* support normal pydantic basemodels
Some checks failed
Actions / Lint (pull_request) Failing after 22s
Actions / Build (pull_request) Successful in 33s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Build Documentation (pull_request) Failing after 37s
5354f56be8
make a shallow copy before mutating dictionary
Some checks failed
Actions / Build (pull_request) Successful in 12s
Actions / Lint (pull_request) Failing after 32s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Build Documentation (pull_request) Failing after 38s
96e60c15b0
implement proper serialization
Some checks failed
Actions / Lint (pull_request) Failing after 25s
Actions / Build (pull_request) Successful in 35s
Actions / Run Tests (pull_request) Successful in 37s
Actions / Build Documentation (pull_request) Failing after 38s
0d3c7c24d3
oops
Some checks failed
Actions / Run Tests (pull_request) Successful in 14s
Actions / Lint (pull_request) Failing after 33s
Actions / Build (pull_request) Successful in 34s
Actions / Build Documentation (pull_request) Failing after 39s
b39546642c
adjust error message
Some checks failed
Actions / Lint (pull_request) Failing after 24s
Actions / Build (pull_request) Successful in 34s
Actions / Run Tests (pull_request) Successful in 36s
Actions / Build Documentation (pull_request) Failing after 38s
cd4d5f0ab1
use the correct "cog" name for sentinel_config
Some checks failed
Actions / Lint (pull_request) Failing after 22s
Actions / Build (pull_request) Successful in 35s
Actions / Run Tests (pull_request) Successful in 36s
Actions / Build Documentation (pull_request) Failing after 37s
ce062a2cbc
support Mapping
Some checks failed
Actions / Run Tests (pull_request) Successful in 13s
Actions / Lint (pull_request) Failing after 33s
Actions / Build (pull_request) Successful in 34s
Actions / Build Documentation (pull_request) Failing after 37s
3dddc4ae4b
update incorrect docstring
Some checks failed
Actions / Build (pull_request) Successful in 19s
Actions / Lint (pull_request) Failing after 33s
Actions / Run Tests (pull_request) Successful in 36s
Actions / Build Documentation (pull_request) Failing after 37s
5a571ee905
support custom config options
Some checks failed
Actions / Build (pull_request) Successful in 34s
Actions / Lint (pull_request) Failing after 35s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Build Documentation (pull_request) Failing after 41s
abea9ff82e
refactor CustomConfigOption/Group, refactor sentinel type configuration
Some checks failed
Actions / Run Tests (pull_request) Successful in 13s
Actions / Build (pull_request) Successful in 34s
Actions / Lint (pull_request) Failing after 33s
Actions / Build Documentation (pull_request) Failing after 39s
f95501daa5
update __init__.py
Some checks failed
Actions / Build (pull_request) Successful in 13s
Actions / Lint (pull_request) Failing after 34s
Actions / Run Tests (pull_request) Successful in 36s
Actions / Build Documentation (pull_request) Failing after 39s
54de759bf9
support more types; use pydantic for serialization
Some checks failed
Actions / Build Documentation (pull_request) Failing after 26s
Actions / Lint (pull_request) Failing after 34s
Actions / Build (pull_request) Successful in 34s
Actions / Run Tests (pull_request) Successful in 37s
fab9dce99b
make tidegear.config its own full module instead of a single file
Some checks failed
Actions / Lint (pull_request) Failing after 33s
Actions / Build (pull_request) Successful in 34s
Actions / Run Tests (pull_request) Successful in 36s
Actions / Build Documentation (pull_request) Successful in 51s
5e9427b4dd
remove tidegear.config from tidegear's init
Some checks failed
Actions / Build Documentation (pull_request) Successful in 31s
Actions / Build (pull_request) Successful in 34s
Actions / Lint (pull_request) Failing after 35s
Actions / Run Tests (pull_request) Successful in 35s
d79e573347
remove tidegear.config from tidegear's documentation
Some checks failed
Actions / Build (pull_request) Successful in 15s
Actions / Lint (pull_request) Failing after 34s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Build Documentation (pull_request) Successful in 50s
e99a0d2069
move ConfigMeta to schema & update module docstring
Some checks failed
Actions / Build Documentation (pull_request) Successful in 28s
Actions / Build (pull_request) Successful in 34s
Actions / Lint (pull_request) Failing after 33s
Actions / Run Tests (pull_request) Successful in 37s
ac425e55c7
add some docstrings; import cleanup; and rename a kwarg parameter
Some checks failed
Actions / Build Documentation (pull_request) Successful in 29s
Actions / Build (pull_request) Successful in 34s
Actions / Lint (pull_request) Failing after 32s
Actions / Run Tests (pull_request) Successful in 36s
739fd3992d
move Sentinel's config schema to its own file
Some checks failed
Actions / Lint (pull_request) Failing after 32s
Actions / Build (pull_request) Successful in 33s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Build Documentation (pull_request) Successful in 49s
001629338c
combine errors that occur during schema registration
Some checks failed
Actions / Lint (pull_request) Failing after 22s
Actions / Build (pull_request) Successful in 34s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Build Documentation (pull_request) Successful in 50s
6f7f67ae30
add pyright comment
Some checks failed
Actions / Build (pull_request) Successful in 12s
Actions / Lint (pull_request) Failing after 32s
Actions / Run Tests (pull_request) Successful in 34s
Actions / Build Documentation (pull_request) Successful in 50s
2c869c71b9
implement tests
Some checks failed
Actions / Lint (pull_request) Failing after 32s
Actions / Build (pull_request) Successful in 34s
Actions / Run Tests (pull_request) Failing after 25s
Actions / Build Documentation (pull_request) Successful in 50s
6d4e1b3702
fix tests
Some checks failed
Actions / Build (pull_request) Successful in 13s
Actions / Run Tests (pull_request) Failing after 24s
Actions / Lint (pull_request) Failing after 33s
Actions / Build Documentation (pull_request) Successful in 28s
a6ce2a623c
actually fix tests
Some checks failed
Actions / Lint (pull_request) Failing after 23s
Actions / Build (pull_request) Successful in 33s
Actions / Run Tests (pull_request) Successful in 34s
Actions / Build Documentation (pull_request) Successful in 50s
320de2dc6c
don't open in binary mode
Some checks failed
Actions / Build (pull_request) Successful in 12s
Actions / Lint (pull_request) Failing after 32s
Actions / Run Tests (pull_request) Successful in 34s
Actions / Build Documentation (pull_request) Successful in 49s
aefad0aad6
fix pytest deprecation warning
Some checks failed
Actions / Build Documentation (pull_request) Successful in 30s
Actions / Build (pull_request) Successful in 35s
Actions / Lint (pull_request) Failing after 34s
Actions / Run Tests (pull_request) Successful in 35s
2407a57048
make tests pass on windows (in theory)
Some checks failed
Actions / Run Tests (pull_request) Successful in 14s
Actions / Build (pull_request) Successful in 34s
Actions / Lint (pull_request) Failing after 33s
Actions / Build Documentation (pull_request) Successful in 27s
1526ea6f14
add tests for GuildConfigOption
Some checks failed
Actions / Build (pull_request) Successful in 13s
Actions / Lint (pull_request) Failing after 32s
Actions / Run Tests (pull_request) Successful in 36s
Actions / Build Documentation (pull_request) Successful in 49s
683b7f1e43
rename a couple tests
Some checks failed
Actions / Build (pull_request) Successful in 12s
Actions / Lint (pull_request) Failing after 32s
Actions / Run Tests (pull_request) Successful in 34s
Actions / Build Documentation (pull_request) Successful in 51s
a23b2c1eb3
do proper typechecking and validation on the version classvar
Some checks failed
Actions / Build (pull_request) Successful in 14s
Actions / Lint (pull_request) Failing after 34s
Actions / Run Tests (pull_request) Successful in 36s
Actions / Build Documentation (pull_request) Successful in 51s
777d4ee81c
simplify two conditionals
Some checks failed
Actions / Build Documentation (pull_request) Successful in 27s
Actions / Build (pull_request) Successful in 34s
Actions / Lint (pull_request) Failing after 33s
Actions / Run Tests (pull_request) Successful in 37s
d532d21f6f
add migrations
Some checks failed
Actions / Build (pull_request) Successful in 34s
Actions / Lint (pull_request) Failing after 34s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Build Documentation (pull_request) Successful in 50s
ba4378caeb
catch when the user forgets to change config_version during migration
Some checks failed
Actions / Run Tests (pull_request) Successful in 16s
Actions / Build (pull_request) Successful in 34s
Actions / Lint (pull_request) Failing after 34s
Actions / Build Documentation (pull_request) Successful in 52s
e8d66312f3
write a whole bunch of documentation and do some other stuff too i guess
All checks were successful
Actions / Build (pull_request) Successful in 33s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Lint (pull_request) Successful in 45s
Actions / Build Documentation (pull_request) Successful in 51s
42b1e99b79
oops
All checks were successful
Actions / Build (pull_request) Successful in 33s
Actions / Run Tests (pull_request) Successful in 35s
Actions / Lint (pull_request) Successful in 44s
Actions / Build Documentation (pull_request) Successful in 49s
de9de9c904
add missing docstring
All checks were successful
Actions / Lint (pull_request) Successful in 21s
Actions / Build (pull_request) Successful in 34s
Actions / Run Tests (pull_request) Successful in 36s
Actions / Build Documentation (pull_request) Successful in 50s
200efe1537
add BaseConfigSchema.options as a cached property
All checks were successful
Actions / Run Tests (pull_request) Successful in 15s
Actions / Build (pull_request) Successful in 34s
Actions / Lint (pull_request) Successful in 45s
Actions / Build Documentation (pull_request) Successful in 29s
01cfbd6627
rename options to registered_options to hopefully avoid namespace collision
All checks were successful
Actions / Lint (pull_request) Successful in 21s
Actions / Build (pull_request) Successful in 33s
Actions / Run Tests (pull_request) Successful in 36s
Actions / Build Documentation (pull_request) Successful in 26s
97cc376e41
enhance type safety; fix a serialization bug relating to pydantic models
All checks were successful
Actions / Build (pull_request) Successful in 36s
Actions / Run Tests (pull_request) Successful in 38s
Actions / Lint (pull_request) Successful in 49s
Actions / Build Documentation (pull_request) Successful in 55s
97af1ab4eb
Author
Owner

I just want to get this merged, so configuration views / menus will come later. The core configuration system is done.

I just want to get this merged, so configuration views / menus will come later. The core configuration system is done.
Merge branch 'main' into feat/config/init
All checks were successful
Actions / Run Tests (pull_request) Successful in 16s
Actions / Build (pull_request) Successful in 34s
Actions / Lint (pull_request) Successful in 47s
Actions / Build Documentation (pull_request) Successful in 54s
b7ff7189ef
cswimr changed title from WIP: Configuration system to Configuration system 2025-12-08 17:14:38 -05:00
cswimr scheduled this pull request to auto merge when all checks succeed 2025-12-08 17:14:57 -05:00
cswimr merged commit 2044b7edf1 into main 2025-12-08 17:15:23 -05:00
cswimr deleted branch feat/config/init 2025-12-08 17:15:23 -05:00
cswimr referenced this pull request from a commit 2025-12-08 17:15:24 -05:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cswimr/tidegear!49
No description provided.