Skip to content

Refactor#111

Draft
Ntalcme wants to merge 117 commits into
mainfrom
refactor
Draft

Refactor#111
Ntalcme wants to merge 117 commits into
mainfrom
refactor

Conversation

@Ntalcme

@Ntalcme Ntalcme commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

No description provided.

Ntalcme added 30 commits June 30, 2026 16:57
* Fix(lang/help): wrong pronoun used #102

* refactor(crownicles): dedupe data loaders via cachedPromise helper

Replace the promise-cache-and-evict boilerplate, copy-pasted across 6 loaders,
with a single cachedPromise() helper in data/source.ts. The five single-value
loaders (loadNames, loadItemIcons, loadMapTypeIcons, getCrowniclesMapGraph,
getContinentGraph) memoize through it; getItems keeps a per-category Map of
memoized loaders. Caching, deduplication of concurrent calls and
eviction-on-failure now live in one place.

Also drops the now-dead module-level promise variables, restores the export
accidentally lost on loadItemIcons, and documents the helper.

Closes #58

* refactor(crownicles): share the CrowniclesIcons parser between loaders

Extract crownicles-icons-source.ts (ICONS_SOURCE_PATH, fetchIconsSource,
extractIconBlock) so the item and map-type icon loaders stop each
re-implementing the `} = {` split + brace-matching + pair regex. The key
pattern is parameterized (\d+ for item ids, default \w+ for map codes), so
behaviour is unchanged.

Closes #61

* docs: tidy comments and fill JSDoc gaps

The codebase was already well-commented, so this is a focused pass:
- document computeLeagueBonusScore (as a Crownicles-formula mirror) and
  CROWNICLES_LEAGUES / CrowniclesLeague
- rewrite the terse CrowniclesMap* interface docs and the HelpState path*
  field comments (incl. the "choosed" typo); drop the redundant `// BFS` marker
- add concise JSDoc to ItemCategory, TitleSize, TextInputStyleName, the /mail
  modal ids, the reminder result types, pathfinderPage and the mail-repository
  helpers; move the misplaced homePage doc onto the export

Closes #79
Closes #80
Closes #81
Closes #82
Closes #83
Ntalcme added 30 commits July 6, 2026 19:43
Adds the data command (slash + prefix), backed by a new gdpr-repository
exposing GdprExport and a 31-day request cooldown. Ban hashes and the
cooldown are now keyed by a salted hash of the Discord id (shared via
core/security/hash-user-id) instead of pseudoId/userId, so both survive
data erasure. Updates the privacy policy to match what is actually
collected, and factors the register/rank command duplication between
the slash and prefix fronts using the pipeline's gate mechanism.
- Remove the command gate/onGateDenied hook; inline register's
  "already registered" check without the non-null assertion / TOCTOU
- Share the denial-handler wiring between the slash and prefix fronts
- Wrap the whole pipeline so a failed denial reply also surfaces
  through onUnexpectedError instead of being lost
- Add owner-only maintenance command (slash + prefix) to drive
  maintenance mode, which had no runtime toggle
- Restrict data to dm scope so the prefix front stops publishing
  personal GDPR data in public channels
- Make bans idempotent and drop the duplicate rank write in banUser
- Read a user's language and rank in a single query (getUserProfile)
- Remove dead code (safeReply*, delete-data exemption) and unify
  factory naming, barrel imports, prefix exports and JSDoc
- Add a generic TtlCache: LRU eviction (bounded memory) and a per-entry
  TTL (stale rows self-heal after out-of-band edits)
- Replace the per-field user maps with a single User-row cache so
  language and rank share one entry and one invalidation
- Route the legal-acceptance check through it, caching hits and misses
…110

Rank, RANK, isRank, NotBannedRank, AssignableRank, ASSIGNABLE_RANKS and
isAssignableRank were declarative types with no authorization decision
attached, but lived in core/permissions/rank.ts and leaked into
repositories, context and pipeline layers that only ever used them as
plain data - mirroring how SupportedLocale already lives in
core/types.ts. Authorization is now an alias of NotBannedRank instead of
a duplicate Exclude<Rank, 'banned'>. hasRank stays the only export left
in core/permissions/rank.ts, since it is the one function actually used
to make an authorization decision.
usecases/ and presentations/ scattered each command's logic across two
parallel flat directories, and adding a command meant touching a barrel
file in each. Each command now owns a discord/features/<name>/ folder
with <name>.service.ts (was usecases/) and <name>.ui.ts (was
presentations/); legal/ has no .ui.ts since it reuses register's.
commands/slash/ and commands/prefix/ stay flat since the runtime loader
discovers commands via a directory scan, not static imports.

lang/en/ and lang/fr/ stay in place too, since grouping strings by
locale is what you actually want when reviewing translations - but
adding a command previously meant editing lang/en/index.ts AND
lang/fr/index.ts separately, and forgetting one only fails silently for
that locale. lang/packs.ts now lists both locales for a command side by
side, so there is exactly one place to touch and no way to add one
locale without the other.
French and English disagree on which count is singular - French treats
0 as singular ("0 jour") while English treats it as plural ("0 days") -
so a naive count === 1 check is wrong for French. plural() delegates to
Intl.PluralRules, which already encodes each locale's real CLDR rule.
Not wired into any message yet; ready for the first command that needs
it.
Latency was interpolated as a raw number ("1234ms"), the one spot in the
bot that displays a runtime number to users. formatNumber() delegates
to Intl.NumberFormat so it reads "1,234ms" in English and "1 234ms" in
French, matching each locale's convention.
buildGdprExport went through getUserProfile, whose cache fabricates and
stores en/normal for anyone with no User row - fine for permission
checks, but wrong for a GDPR disclosure, which must report what is
truly stored. findStoredUserProfile reads the row directly, uncached,
and returns null instead of a default. GdprExport.language/rank are now
nullable; the export shows a single "nothing stored" notice when there
is truly nothing (row, legal acceptance, ban hash all absent) - reachable
since data is exempt from the legal gate.
/data and c!data take an optional target now, owner-only: a Discord user
option on the slash side, a mention/id argument or an interactive
UserSelectMenu picker on the prefix side (which has neither). Looking up
someone else's data never touches their own 31-day cooldown - it isn't
their request. Delivery matches what each path can do: the slash option
and the picker's ephemeral component-interaction reply need nothing
extra; the mention/id argument goes through the prefix front's existing
DM relay, since a plain message reply can't be ephemeral.

sendResponseToInteraction now takes any RepliableInteraction instead of
just ChatInputCommandInteraction, so the picker can reuse it.
…110

When reduce() called both markHandled() and stop(), neither re-render
happened: the inline update() was skipped because the interaction was
already handled, and the end handler assumed stop() had already
rendered the disabled state, so it skipped too. disabledRendered now
tracks whether that render actually reached the message, so the end
handler only skips when it's true.

Also: unauthorized clicks now go through the collector's own filter
instead of being checked inside the 'collect' handler, so they no
longer reset the idle timer or count toward the collected items -
discord.js only does either for interactions the filter accepts. The
refusal reply moves to the 'ignore' event, which fires for whatever the
filter rejects.
Let a command response be an ordered list of top-level Components V2
entries (containers, action rows, sections, text, separators) instead of
a single container, so a message can mix them in any order. Every fluent
component now exposes build(), CommandResponse gains a components list
beside the single-container sugar, and the interactive-message runtime
renders either shape. Rename EmbedColor to ColorName to match its use as
a component accent, not just an embed colour.
Render the "DRING" message as a container (content + owner mention) plus a
separate relaunch action row below it, using the new top-level component
API. Rename buildReminderTriggeredContainer to buildReminderTriggeredComponents,
give the relaunch button its own icon, and let delivery forward whichever
shape render returns.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: high Urgent, do first refactor Refactoring of code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant