Skip to content

Add Recent Chats - #221

Open
Alex979 wants to merge 6 commits into
SteamClientHomebrew:mainfrom
Alex979:add-recent-chats
Open

Add Recent Chats#221
Alex979 wants to merge 6 commits into
SteamClientHomebrew:mainfrom
Alex979:add-recent-chats

Conversation

@Alex979

@Alex979 Alex979 commented Aug 13, 2026

Copy link
Copy Markdown

Summary

Adds "Recent Chats" to the Millennium Plugin Database as a submodule.

Plugin repository:
https://github.com/Alex979/Steam-Recent-Chats-plugin

Features

  • Adds a Chats tab to Steam's Friends window on both the desktop and in-game overlay.
  • Sorts conversations by their latest activity.
  • Shows the latest message, including game, lobby, Steam Playtest, trade, and broadcast invites.
  • Displays timestamps and unread counts without changing Steam's unread state.
  • Searches names and message previews.
  • Opens Steam's normal chat window when a conversation is selected.
recent-chat-plugin-screenshot

@github-actions github-actions Bot changed the title Add Recent Chats plugin Add Recent Chats Aug 13, 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/tag 6ecbe83b4a5758f91168322c455da927c32468c0 (v0.3.0). I also checked the published recent-chats-0.3.0.zip; its SHA-256 was aefa5b1a4fc27ed258854cb2d06182f5caf15222bd5bfe5dd744437810fe3119, and the installed bundle matched the audited release source.

Runtime results

  • The plugin attached to the desktop Friends window using its primary anchors.
  • Exactly one Chats tab, one panel, and one style element were injected.
  • My test account produced 3 conversation rows with no error state.
  • Searching with a prefix from an existing conversation filtered the list from 3 rows to 1; clearing the query restored all 3.
  • Selecting a read conversation dispatched one Steam chat-window event.
  • Calling the plugin's onDismount removed the tab, panel, and style element completely (all counts returned to zero).
  • I found no Recent Chats warnings/errors in webhelper_js.txt. The plugin is frontend-only, has no custom binaries/backend, and I found no plugin-owned telemetry or external network requests.
  • I deliberately did not record conversation names or message contents while testing.

Issues found

  1. The PluginDatabase build workflow is not currently reproducible from a clean checkout. Under Node 20.20.2, the repository tracks bun.lock but not pnpm-lock.yaml, while PluginDatabase builds with pnpm. A clean pnpm resolution selected @steambrew/ttc@3.3.7 from ^3.2.6 and TypeScript 7.0.2; pnpm run build failed, including TS5108 (moduleResolution=node10 was removed) and a rollup-plugin-inject-process-env module-resolution error. Please commit a pnpm lockfile or pin a known-compatible toolchain, then verify a clean Node 20 pnpm install && pnpm run build.
  2. The $schema URL in plugin.json currently returns 404 (src/sys/plugin-schema.json has moved), so editor validation is not working.

The live UI behavior and unload cleanup looked solid. I would treat the clean pnpm build as the main pre-publication blocker.

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

Alex979 commented Aug 14, 2026

Copy link
Copy Markdown
Author

Thanks @Shightrox for the detailed testing and feedback! Both reported issues should now be addressed in v0.3.1, and I've updated this PR to point the submodule to commit 77d0bfc.

Changes made

  • Added and committed pnpm-lock.yaml alongside bun.lock.
  • Pinned all direct dependencies to exact versions, including TypeScript 6.0.3 and @steambrew/ttc 3.3.7.
  • Updated the TypeScript configuration to use moduleResolution: "bundler".
  • Added pnpm build-script approvals for @parcel/watcher and esbuild.
  • Corrected the schema URL to src/system/plugin-schema.json.

I verified this from a clean checkout using Node 20.20.2, pnpm 10.34.5, CI=true, and NODE_ENV=production. pnpm install reported that the lockfile was up to date and skipped dependency resolution, and pnpm run build completed successfully. The previous TS5108 and Rollup module-resolution errors no longer occur.

One separate, repository-level issue I noticed while reproducing the workflow: PluginDatabase pins Node 20 but installs an unpinned pnpm using npm install -g pnpm. As of this test, that installs pnpm 11.21.0, which requires Node >=22.13 and crashes under Node 20.20.2 with ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite before plugin dependencies are installed. I have not changed the PluginDatabase workflow since that is outside the scope of this contribution, but wanted to mention it in case CI encounters it. The original test may have used an already-installed Node-20-compatible pnpm version.

@shdwmtr shdwmtr added the in-progress The plugin has issues needed to be addressed. label Aug 16, 2026
@shdwmtr

shdwmtr commented Aug 16, 2026

Copy link
Copy Markdown
Member

Remove content: <SettingsContent />, given there is no actual configurable content living there.

@Alex979

Alex979 commented Aug 16, 2026

Copy link
Copy Markdown
Author

Remove content: <SettingsContent />, given there is no actual configurable content living there.

Done, updated to v0.3.2 which completely removes content: <SettingsContent />,

@shdwmtr

shdwmtr commented Aug 16, 2026

Copy link
Copy Markdown
Member

Another issue I didn't initially notice:

you should re-use Steams styled components instead of re-styling them manually. Right now, all themes would have to updated to support this tab.

Just open the inspector, steal Steams real classname, then checkout the Steam source tracker, you can simply CTRL+F a minified classname (ex: P6U-me_KbkUhyZ9RAdYpH is mapped to VoiceRequested), and find its internal name, which you can then dynamically find with findClassModule. See Millennium's source tree/other plugins for how its used.

Lmk if this makes sense!

@Alex979

Alex979 commented Aug 19, 2026

Copy link
Copy Markdown
Author

Another issue I didn't initially notice:

you should re-use Steams styled components instead of re-styling them manually. Right now, all themes would have to updated to support this tab.

Just open the inspector, steal Steams real classname, then checkout the Steam source tracker, you can simply CTRL+F a minified classname (ex: P6U-me_KbkUhyZ9RAdYpH is mapped to VoiceRequested), and find its internal name, which you can then dynamically find with findClassModule. See Millennium's source tree/other plugins for how its used.

Lmk if this makes sense!

Makes sense @shdwmtr!

I just updated this PR to v0.4.0, which contains all the changes in Alex979/Steam-Recent-Chats-plugin#1. That PR rewrites the whole plugin to reuse Steam's native style classes. The tab copies its classes straight off the native FRIENDS tab, and the search field, rows, avatars, presence colors, and unread badge all use the same classes that the friends list uses. I used findClassModule for the text tokens and pulled the rest from the inspector like you suggested. More details about this in the linked PR.

This was a fairly significant change but well worth it for theme compatibility. There are some quirks, like how in some cases Steam's built-in classes include unwanted styling like layout & geometry, that I had to explicitly neutralize to avoid breaking my own layout. That does make the plugin a bit more brittle in response to steam updates, so I have every borrowed class documented in docs/steam-class-mapping.md for easier maintenance if future steam updates break some style. The big benefit here is that now the plugin has consistency across many themes. Some screenshots for example:

Steam Default

showcase-default

Minimal Dark

showcase-minimaldark

SpaceTheme

showcase-spacetheme

Material

showcase-material

As you can see, the plugin adapts to various different themes now, and row colors are now based on the users' in-game / online / away / offline presence. Let me know if this is satisfactory or if there are any other changes you'd like to see, thanks!

@Norphirion

Copy link
Copy Markdown

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

Reviewed and built on Windows 11, Steam Client Beta, Millennium v3.5.0-beta.2, Node 24.18.0, pnpm 10.34.5, using the exact pinned commit ad6c11892524835439adb629125f5d102b9e7295 (v0.4.0). I did not install and drive the UI, so this is a source audit, a build reproduction, and a live resolution check performed inside Steam's own JavaScript context. What I ran and what I did not is spelled out below.

@Shightrox already covered the v0.3.0 runtime behaviour, and both blockers from that review are fixed. Rather than repeat it, I focused on the two things that were still open: the privacy surface, which nobody had examined closely for a plugin that reads your messages, and the v0.4.0 native-class rewrite, which landed after that review and has not been tested on Steam Beta.

What stays on the machine

This is the question that matters most for this plugin, so I want to answer it precisely rather than in passing.

I grepped the whole frontend for fetch, XMLHttpRequest, WebSocket, sendBeacon, localStorage, sessionStorage, indexedDB, eval, new Function, innerHTML and dynamic import(). The only hit anywhere in the repository is import('fflate') in scripts/release.ts, which is packaging tooling and is not shipped. There is no backend (useBackend: false) and no custom binary.

Reading is strictly read-only. normalizeRecentChats calls store.ChatStore.GetRecentChats() and then only reads properties, each one wrapped in a safely() guard. chat-actions.ts calls Steam's own UIStore.ShowFriendChatDialog, UIStore.ShowAndOrActivateChatRoomGroup and the SteamClient.WebChat equivalents. Nothing writes, marks read, acknowledges, or otherwise mutates chat state, which supports the "without changing Steam's unread state" claim in the description.

Message text is read into memory to build the one-line preview and never leaves the process. With one exception, below.

The one place message text can escape

frontend/chat-actions.ts:55:

console.warn(LOG_PREFIX, 'No compatible chat-opening method was found.', conversation);

That logs the whole RecentConversation, which carries name (the friend or group display name), snippet (the cleaned message preview) and raw (Steam's underlying chat object). It only fires on a fallback path where no chat-opening method resolved, so it should be rare. But Steam's console output lands in webhelper_js.txt, and that file is exactly what people paste into bug reports.

Logging conversation.id and conversation.kind would identify the failure just as well without carrying a friend's name and their last message into a log file. It is the only change I would call a genuine fix rather than a preference.

Steam Beta, Millennium 3.5.0-beta.2: the borrowed classes still resolve

You flagged the native-class rewrite as making the plugin more brittle against Steam updates, so I checked the dynamic half of it against a newer client than the previous review used. I evaluated the plugin's own predicates inside Steam's shared JavaScript context:

Lookup Result
findClassModule(isNativePersonaTextClassModule) found, 34 keys; playerName resolves to nOdcT-MoOaXGePXLyPe0H
Root store via findModuleExport(isSteamRootStore) found, with ChatStore, AppInfoStore, FriendStore and UIStore all present
ChatStore.GetRecentChats() callable, returns an array
UIStore.ShowFriendChatDialog, ShowAndOrActivateChatRoomGroup both present
SteamClient.WebChat.ShowFriendChatDialog, ShowChatRoomGroupDialog both present

So the runtime resolution and both fallback paths hold on the current Beta.

Two honest caveats. GetRecentChats() returned zero entries because I had not opened the Friends window in that session, so this is a resolution check and not a functional test. And I only verified the dynamically resolved module; the classes copied off the DOM at runtime (friend, avatarHolder, socialListTab, playerName and the rest documented in docs/steam-class-mapping.md) need the Friends window open to check, which I did not do. Since those are Steam's stable non-hashed friends-list names rather than content hashes, they are the less fragile half anyway.

I also deliberately did not read or record any conversation name or message content while probing, only counts and types.

Build

Both of @Shightrox's blockers are genuinely fixed, verified rather than assumed.

pnpm install --frozen-lockfile completes with the lockfile up to date, and pnpm run build succeeds with NODE_ENV=production and CI=true, producing a 29904 byte bundle. That was on Node 24 rather than the Node 20 the CI pins, so it is a second data point next to your own Node 20 verification rather than a replacement for it. pnpm run typecheck also exits 0 across all three tsconfigs.

The $schema URL is fixed and I confirmed it: src/system/plugin-schema.json returns 200. Worth noting for anyone else reading this, because the template URL that most plugins carry (src/sys/...) is the broken one, and I had wrongly concluded on my own PR that the schema had been removed entirely. It has moved, not gone.

Also correct here and worth calling out because it is commonly wrong: .millennium/ is gitignored rather than committed, no metadata.json is tracked, the LICENSE is present and matches package.json, and every direct dependency is pinned to an exact version.

Smaller points

The reconcile loop is a permanent 2 Hz poll. monitorFriendsWindow loops on DOCUMENT_RECONCILE_INTERVAL_MS = 500 for as long as the Friends window is open, calling refreshFallbackTabClasses and updateThemeDetection on every tick, whether or not anything changed. The guards around it are correct, so this is a cost question rather than a correctness one, but backing off once a mount has been stable for a while would cut most of it. The fast 50 ms startup phase is already time-boxed to 5 seconds, so the pattern is clearly deliberate.

poc is still in every injected identifier. recent-chats-poc-tab-host, recent-chats-poc-panel-host, recent-chats-poc-styles, recent-chats-poc-theme-fallback, data-recent-chats-poc-open. These are visible in the inspector, and since the whole point of the v0.4.0 rewrite was theme compatibility, theme authors will end up writing selectors against them. Renaming them after publication would break those themes, so settling on final names now costs nothing and later costs something.

Two lockfiles. bun.lock and pnpm-lock.yaml are both tracked. They are currently in sync, both last touched by 9a4e5a1, so this is only a maintenance note: it is worth saying in the README which one is authoritative, since CI uses pnpm while test and release use bun.

Tests. Five test files, which is more than most submissions here have, and I want to acknowledge that. I could not run them since test is bun test and I do not have bun installed; typecheck covers them for compilation at least.

Nice work overall. The cleanup path in particular is the most careful I have seen in a Millennium plugin, and the class mapping document is the kind of thing that makes a plugin maintainable by someone other than its author.

@Alex979

Alex979 commented Aug 21, 2026

Copy link
Copy Markdown
Author

Thanks for the review @Norphirion! Made these two changes in Alex979/Steam-Recent-Chats-plugin#2:

  • Instead of logging the entire conversation, log just conversation.id and conversation.kind to prevent any leaked convos in logs
  • Removed poc from all classnames, that was leftover from when this was a "proof of concept", which is no longer the case.

@Norphirion

Copy link
Copy Markdown

Re-checked on 37eed4d (v0.4.1), on Windows 11, Steam Client Beta build 1787097529 (2026-08-18), Millennium v3.5.0-beta.2, Node 24.18.0 and pnpm 10.34.5; with SpaceTheme.

Both points are fixed, and I verified rather than took your word for it.

Logging. chat-actions.ts now passes { id, kind } only. I re-audited all eight console.* calls in the frontend and none of them carries a conversation name, a message snippet or the raw Steam object any more.

poc identifiers. All five constants renamed, styles.ts updated across 16 selectors, and a case-insensitive search for poc across the whole repository now returns nothing, including the README note about the old folder.

Nothing regressed alongside it: still no network access, no storage APIs and no dynamic execution anywhere in the frontend. pnpm install --frozen-lockfile, pnpm run typecheck and pnpm run build all pass on Node 24 with pnpm 10.34.5, and the version is consistent at 0.4.1 in both plugin.json and package.json.

Thanks for turning both around so quickly. No further concerns from me :)
image
image
image

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

Labels

in-progress The plugin has issues needed to be addressed. plugin-addition

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants