Skip to content

Add Date added - #218

Open
Norphirion wants to merge 5 commits into
SteamClientHomebrew:mainfrom
Norphirion:add-date-added
Open

Add Date added#218
Norphirion wants to merge 5 commits into
SteamClientHomebrew:mainfrom
Norphirion:add-date-added

Conversation

@Norphirion

@Norphirion Norphirion commented Aug 5, 2026

Copy link
Copy Markdown

Date added

Adds an Added card to the stats row on a game's library page, next to Cloud status, Achievements and Size on disk, showing the date the game entered the user's library.

Steam records this date but never surfaces it anywhere in the client. It makes it easy to tell a years-old backlog entry from something picked up in the last sale.

The date is read from rt_purchased_time on Steam's local app overview: no network access, no account linking, no configuration. Class names are resolved at runtime via findClassModule, so the card keeps Steam's own styling rather than hardcoding hashed class names.

Task Checklist

Developer

  • I am the original author or an authorized maintainer of this plugin.
  • I have complied with all license requirements for the libraries used, including providing appropriate notices where necessary.
  • My plugin is fully open source and does not depend on any external paid services, except for widely trusted and well-known platforms. Additionally, neither I nor anyone associated with me profits from any such services.

Plugin Functionality

  • I have tested the plugin on both the Stable and Beta Steam update channels.
  • My plugin is unique, or provides additional or alternative functionality to plugins already on the store.

Backend Configuration

  • No: I use a standard Millennium python backend in my plugin.
  • No: I use custom binaries that or rely on other FOSS projects that aren't written directly using Millennium's python backend.

Community Contribution

  • I have tested and left feedback on two other plugin pull requests.
  • I have added links to those feedback comments in this PR.

Testing Instructions

  • Verified by a third party on Steam Client Stable.
  • Verified by a third party on Steam Client Beta.

@github-actions github-actions Bot changed the title feat: add date-added Add Date added Aug 5, 2026
@Shightrox

Copy link
Copy Markdown
Contributor

Disclosure: I am the author of #224 and tested this PR as part of the Community Contribution requirement.

Tested on Windows 11, Steam Client Stable, Millennium 3.4.0, using the exact pinned plugin commit b349cac37510647db747971aa5e6267b461d1d56.

Runtime results

  • The plugin loaded without a backend and without making its own network requests.
  • On CS2 (730) and Dota 2 (570), where Steam exposed a positive rt_purchased_time, the injected cards had non-empty text and followed the active app route correctly.
  • On two owned games where that field was absent (105600 and 1604000), no empty/stale card was rendered.
  • I found no Date added warnings or errors in webhelper_js.txt during navigation.
  • Steam exposed two visible GameStatsSection hosts on both tested pages, and the plugin inserted a card into both. This may be intentional for responsive layouts, but it is worth manually confirming that the user never sees a duplicate in any library layout.

Issues found

  1. The PluginDatabase build workflow is not currently reproducible from a clean checkout. Under Node 20.20.2, using the database's pnpm flow, the repository has no tracked pnpm-lock.yaml (only package-lock.json). pnpm install therefore resolved @steambrew/ttc from ^3.2.6 to 3.3.7 and its TypeScript peer to 7.0.2; pnpm run build failed, including TS5108 because moduleResolution=node10 is removed in TS 7, followed by a rollup-plugin-inject-process-env resolution error. Please commit a pnpm lockfile or pin a known-compatible toolchain and verify a clean Node 20 pnpm install && pnpm run build.
  2. There is no unload cleanup. start() creates a permanent one-second interval, but the plugin returns no onDismount; removeCards() removes React hosts without calling root.unmount(). Disabling/reloading the plugin can therefore leave the interval and React roots alive. Returning a cleanup function that clears the interval, unmounts each root, and removes the hosts would make hot reload/disable safe.
  3. plugin.json reports 1.0.1, while package.json reports 1.0.0.
  4. The $schema URL currently returns 404 (src/sys/plugin-schema.json has moved), so editor validation is not working.
  5. The package declares MIT and the README says MIT, but the pinned repository has no LICENSE file. Adding the actual license text would make the licensing claim verifiable.

The core feature behaved correctly in the routes I tested; the clean-build and lifecycle points are the main things I would address before publishing.

@Shightrox Shightrox mentioned this pull request Aug 14, 2026
9 tasks
@Norphirion

Copy link
Copy Markdown
Author

Thank you for this review @Shightrox, the depth of it is genuinely appreciated. Testing on real apps, checking the negative cases (105600, 1604000), reading webhelper_js.txt and actually attempting a clean build is far more than a rubber stamp, and every one of your five points was valid.

Some context that may be useful: I'm not a developer. This plugin was written with Claude Code, and I reviewed and tested the result rather than authored it line by line. So I apologise for the rough edges you had to find, especially the build and lifecycle ones, which are exactly the kind of thing I lacked the background to catch. I'll do my best to address anything else you spot.

All five points are fixed in the updated commit. Details below.

1. Clean build under pnpm

This was the real bug, and your diagnosis was exactly right. typescript was never declared as a dependency at all - it was only present transitively - so pnpm was free to satisfy @steambrew/ttc's peer with TypeScript 7, which is where TS5108 came from.

Fixed by:

  • Pinning every devDependency to an exact version (no ^).
  • Declaring typescript explicitly at 5.9.3, plus a pnpm.overrides entry so it cannot be hoisted to a different major anywhere in the tree.
  • Committing a pnpm-lock.yaml.
  • Switching moduleResolution from node to bundler, which both TypeScript 5 and 7 accept — so the build no longer depends on the resolver that broke for you.
  • Dropping @steambrew/api and @steambrew/webkit, which were declared but never imported.

Verified rather than assumed: from a clean checkout containing only the tracked files, pnpm install --frozen-lockfile followed by pnpm run build succeeds and produces a bundle byte-identical to the committed one. That was with pnpm 9 on Node 24 rather than your Node 20.20.2, so if it still misbehaves on 20 I'd very much like to know.

2. No unload cleanup

Correct, and worse than it looked: the plugin returned nothing at all from definePlugin, with a type cast to force that past the compiler. Plugin has supported onDismount all along.

The plugin now returns a proper Plugin object whose onDismount clears the interval, unmounts every React root and removes the hosts. Two related details:

  • The roots were held in a WeakMap, which cannot be enumerated at teardown, so it is now a Map.
  • That change would have introduced a slower leak of its own, since React detaches our hosts whenever it re-renders the stats section. Each tick now prunes roots whose host is no longer connected and unmounts them.

Only icon is returned, so no settings panel is mounted. Millennium requires title, icon and content together for that, and this plugin has nothing to configure.

3. Version mismatch

Fixed; plugin.json and package.json both read 1.0.1.

4. $schema 404

Removed rather than repointed, and I think this one is worth flagging upstream.

src/sys/ no longer exists in the Millennium repository at all, and I could not find a plugin-schema.json anywhere else in it, it appears to have gone during the move to Starlight. The official PluginTemplate and the other published plugins I checked still carry the same dead URL.

Since $schema is editor-only metadata with no runtime effect, dropping the line seemed better than pointing at a URL I'd be guessing at. Happy to add the correct one if it exists somewhere I didn't look.

5. Missing LICENSE

Added : the MIT text, matching what package.json and the README already claimed.

On the two GameStatsSection hosts

Good catch, and worth documenting. I measured both on a game page:

  • One sits under an ancestor with opacity: 0 — it is the sticky PlayBar that fades in when you scroll. checkVisibility() returns false for it, and elementFromPoint at its centre does not land inside it.
  • The other has nothing hiding it and is the one actually on screen.

So only one is ever visible and the user never sees a duplicate. Injecting into both is deliberate: if the card only went into the in-page section, it would disappear the moment the sticky bar takes over on scroll. I've added a comment in the source explaining this so it doesn't look accidental to the next reader.

Thanks again, the plugin is in noticeably better shape than before your review.

@shdwmtr

shdwmtr commented Aug 16, 2026

Copy link
Copy Markdown
Member

The .millennium/ directory should be ignored by git. The PluginDatabase will create that automatically.

@Norphirion

Copy link
Copy Markdown
Author

Thanks for catching it, I had this backwards.

.millennium/ is now in .gitignore and untracked in both of my plugins.

The README no longer tells people the bundle has to be committed.

@PoorPocketsMcNewHold

PoorPocketsMcNewHold commented Aug 17, 2026

Copy link
Copy Markdown

Project contain a malicious NPM package.

󰣇  …/date-added   main   v26.7.0   11:00  
 npm install
npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm warn deprecated glob@7.2.3: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
npm warn deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead
npm warn deprecated glob@11.1.0: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
npm warn deprecated glob@8.1.0: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
npm error code E403
npm error 403 403 Forbidden - GET https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz
npm error 403 In most cases, you or one of your dependencies are requesting a package version that is forbidden by your security policy, or on a server you do not have access to.
npm error A complete log of this run can be found in: /home/pm/.npm/_logs/2026-08-17T09_00_36_514Z-debug-0.log

✗ Malicious package blocked

  - fs@0.0.1-security
    Reference: https://app.safedep.io/community/malysis/01K32N96RPJWBP3M37FVWGWXWF

✗ PMG: 197 packages analyzed, 1 blocked
↳ pmg: npm exited with code 1

https://github.com/Norphirion/date-added/blob/main/pnpm-lock.yaml#L1307

Norphirion/date-added#1

@Norphirion

Copy link
Copy Markdown
Author

Thanks for running a supply-chain scan @PoorPocketsMcNewHold
I'd much rather you flagged this than stayed quiet. I've dug into it, and I believe it's a false positive, but the inconvenience on your side is real. Evidence below so you can check rather than take my word for it.

What the package is. fs@0.0.1-security is npm's own placeholder for the squatted fs name. Its repository is github.com/npm/security-holder, and its README says npm holds the name "to avoid malicious use". The published tarball contains exactly two files — package.json and README.md — with no code, no install scripts, and not even the index.js its own manifest points at. It also cannot be loaded at runtime: fs is a Node builtin, so require('fs') resolves to the core module no matter what sits in node_modules.

Where it comes from. Not from this plugin:
date-added@1.0.1 └── @steambrew/ttc@3.3.7 └── fs@0.0.1-security

@steambrew/ttc is Millennium's own build toolchain, and it declares fs as a direct dependency. I checked every version published to npm — all 32 of them, from 1.0.0 through 3.3.7 — and the dependency is present in every single one. There is no version of ttc that avoids it.
Why it isn't specific to my plugin. Other plugins already in the database pull the same thing, because they use the same compiler:

  • size-on-disk@steambrew/ttc@^2.8.7
  • non-steam-playtimes@steambrew/ttc@^2.8.7
  • steam-share@steambrew/ttc@^3.2.6
  • extendium@steambrew/ttc@~3.2.6
    The official PluginTemplate declares @steambrew/ttc@^3.2.6 too, so anyone following it from scratch inherits it as well.
    I genuinely can't tell you why it surfaced here and not on those — that's a question about which reviews got scanned with which tooling, and I have no visibility into it. What I can show is that the package is not something I added, and not something I can remove from my own manifest.
    What I think should happen. The clean fix is upstream: ttc shouldn't list fs at all, since it's a Node builtin and the entry does nothing but drag in a placeholder that trips scanners. I'm happy to open an issue on the Millennium repository — it would clear this for every plugin author, not just me — unless a maintainer would prefer to handle it directly.
    If you'd rather I neutralise it on my side in the meantime, I can add a package-manager override so the placeholder is never fetched. I've deliberately not done that yet: it would paper over an upstream packaging bug in a way every future plugin author would have to copy without knowing why. Say the word and I'll add it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants