Beeminder Forum

I made an online version of TagTime

Hi there! I’ve been working on a new open-source, web-based version of TagTime, called TagTime Web (my demo instance, but you can and should run your own instance) (source code). It’s basically just TagTime, but online. It supports the Web Notifications API, so you can get a push notification when a ping occurs (Web Notifications aren’t supported by iOS Safari yet, though). You can also have it play a sound when it is ping time. On devices that support it the device will vibrate when it is ping time (untested, since my phone doesn’t support it and I can’t feel vibration on emulators (if your phone supports it: is the vibration too long?)).

You can import or export from a TagTime log file, and export (but not easily import) the internal SQLite database used to represent your pings.
Some other neat features include

  • Fast loading times (it doesn’t need to sync your entire ping history to the browser)
  • Tag autocomplete
  • Custom interval/seed
  • Each tag gets a unique colour based on the hash of it’s name
  • Tags are converted to lowercase
  • Dark mode and light mode
  • Edit pings without needing to load your entire ping history in the Pings tab (you can view pings in a time range, and thanks to server-side pagination you don’t need to download everything at once) (currently the Pings tab might have a few size issues on mobile, I’m working on improving tag input on smaller screens)
  • It works on most modern browsers on both desktop and mobile
  • It works offline (although if you answer pings differently on different devices one answer will overwrite the other)
  • You can add it to the homescreen and use it like an app (it’s a PWA)

It uses a new ping algorithm that determines the seeded FNV-1A hash of the current Unix time in seconds as a 0-1 fraction, and pings on a second if that second’s fraction is less than reciprocal of the interval in seconds. (source code for this is in the taglogic directory)

The frontend and backend logic is written in JavaScript and TypeScript, the frontend UI is written in Svelte, the ping algorithm is written in Rust and compiled to WASM. The source code is on Github. (be warned: the code is a bit messy in some places for now).

There are currently a few important issues:

  • It doesn’t support older browsers. (If this causes problems tell me what browser version you are trying with and I might be able to add support for it.)
  • It silently fails if you use more than 40 MiB (if you host it yourself you can change the limit in the config.json file)

I intend to add more features to TagTime Web over time, including adding some graphs and more features.

Thanks for reading, let me know what you think of this!


Hi there! I’ve made some updates recently. Here they are:

  • Version numbering in the footer. If you see “v53” (or higher, if you are from the future) at the start of the footer, then you have these updates. If you haven’t got them, reload, close all open TTW tabs and re-open them. This should force an update.
  • Better ping filtering, used on the Pings page and on graphs. Now you can specify if all the tags you specify should be included, or just one.
  • Bug fixes for pagination in the pings page
  • Added an indicator to show if pings are synced
  • A graphs page, based off these ones
  • Made a demo video (slightly outdated)

I’m currently working on adding more graphs and an integrated todo list.

Here are some graphs it can generate (you might want to disable pagination when generating graphs to get all data ever, or set a time range):


Awesome work! Any chance you’d consider the official TagTime universal ping schedule?


I’ve added support for the universal ping schedule. There is now a button in Settings to use it (make sure you see v60 (or higher) in the footer, if not try closing your browser and re-opening it (or just closing all TTW tabs, waiting a few seconds, and re-opening them (or just clearing your cache))):

"Use the universal schedule" button

Note that due to the way I’ve implemented it, if you are using the original TagTime algorithm but not the universal schedule (different seed/interval (seed+algorithm are configured under Advanced)) then it will be slower. (This is because I’ve pre-generated a lookup table for each 5 day interval for the universal schedule. When on the universal schedule instead of starting from URPING it maps the earliest time it cares about to an index in the lookup table, and populates the RNG state based on that, for more details see the Rust source code).

If you any more suggestions, please tell me!


A note about your comment on line 85:

That’s not actually considered a compiler bug. That line is indeed unused, when your code is compiled normally (not compiled in test mode.) The way to avoid that warning is to tell the compiler not to bother compiling that module in test mode—that is, put a #[cfg(test)] before the module, on line 82.

That’s actually the main reason why tests are generally put in their own module to begin with—you could put the tests at the top level without the mod test block, but then you wouldn’t have anywhere to put the #[cfg(test)] attribute! You want to have the attribute not just because it avoids the unused code warning, but also because it saves you compile time, telling the compiler to skip that module entirely.

(Also, it saves you space in the compiled binary—it’s rather pointless to have the tests compiled and included in the resulting binary even when they aren’t run.)

One could imagine an alternate universe where the compiler tries to “do what you mean” and does the equivalent of adding that attribute to your test module for you. But the Rust compiler as it stands doesn’t do that, and for what I think are good reasons. Explicit is better than implicit, or the anti-magic principle, for one. You don’t want the compiler doing “magic” based on circumstantial facts like the name of the module (test is just conventional, it’s not special cased), or the fact that the module contains only tests (because the second you add a helper function for your tests, the behavior would silently change.) It’s a very good thing that the Rust compiler has chosen to act consistently—all functions in all modules are compiled, unless you specify conditional compilation with #[cfg()] (or #[cfg_attr()]). This lets you have confidence that you know exactly what’s being compiled (and what goes into the resulting binary) without any guesswork, or any weird rules you have to memorize.

All this is the long way to say: just add #[cfg(test)] to line 82, and you can remove line 84 and the comment on line 85, and the issue there will go away.


Thanks, I’ve fixed it.


Here’s another update:

  • You have been logged out due to a authentication revamp, because…
  • You can change your password now (and also /.well-known/change-password is supported on the backend server (unlike Beeminder btw))
  • The homepage you get when you are logged out now has content (instead of 1 line of text that kinda explains it)
  • Updated dependecies
  • There is now a warning that your email address in un-changeable
  • The main content is now in a semantically correct <main> tag
  • Bugfix: sometime between now and last deploy it errored when built in production mode, which turned out to be an upstream bug (with svelte-loader) that I that I fixed
  • Bugfix: sometimes notifications would stop arriving
  • Bugfix: notifications came at the wrong time if you were using the original scheduling algorithm
  • Bugfix: there used to be a memory leak that leaked memory but very slowly (it only started to be an issue after running the server for more than a day or two), but I think I’ve gotten rid of it

If you have any issues or features you want, please tell me!


Great work!
For my use case I need to send Windows push notification to Iphone while afk.
Tried Snarl and managed to get test via Prowl, but don’t know how to listen for web notification in Snarl (failed to find documentation). Not technically savvy, so would appreciate any advice.

I have played with TagTime Web for a while. Good effort! Some things that I have noticed:

  • When trying to login via a private tab, TTW gets stuck at the spinner.
  • When switching to the universal ping schedule TTW keeps asking for another Ping (not on the schedule). I add a screenshot for illustration below.
  • Do you mind adding some CSS or use Bootstrap? That would be kind.
  • In the graph view, have you considered plotting different tags in different colors? I want to compare the time spent working on project X vs. time wasted on Twitter, for example.

Have you considered adding an API? It would be cool if TTW could be used to synchronize Pings between multiple devices.



  • You should be able to get private tabs to work by allowing 3rd party cookies:
    (Chrome here is worried that is able to track you, even when you are using, a different origin. I’m working on not using 3rd party cookies but for now you have to allow them)
  • I’ll look into the universal schedule issue
  • yeah the CSS could be better, I’ll work on it
  • Oh yes, coloured tags in the graph view is a great idea!
  • An API would indeed be cool! There kinda is one already, I just need to document it and add a way to authenticate problematically

thanks for the feedback, I really appreciate it!


Here are some updates for the latest version (v225 and up, in the footer):

  • Beeminder integration! (video explanation)
    • You have to create a manual goal, then link it up with TTW
    • The goal unit should be “hours” (which causes Beeminder to automatically use HH:MM formatting)
    • Autofetch callbacks aren’t supported (actually not that important, but a long explanation about API-related issues is coming soon)
  • Redesigned the bar at the top to make it more visually appealing
  • Multiple bug fixes
  • Started working on the API documentation (although it’s not really possible to use the API yet)

I’ve started having problems with not getting notifications. (I hope) it started only now that I’ve changed the interval to 15 minutes. It works (it seems) if the tab is open; but even though the app is installed, if it’s not open it won’t ping me. This seems to be unreliable across devices; I have an android tablet and a smartphone, two laptops and one workstation, and yet sometimes I’ll hear no notifications for a ping (sometimes, only a few of the devices or only one or no one will notify me).

How would I debug this?

Disabling notifications and re-enabling them might help. It seems like the notification subscription somehow didn’t properly get to the server, so when it’s re-enabled it will re-subscribe to notifications (hopefully).

I’ll go through another round of that. Will report back :slight_smile:

… in the meantime, to also better integrate it with my vocal assistant, can I poll your backend like the client does when you refresh the pings page?

Sure, go ahead! I just rolled out support for using API tokens (instead of using cookies) in v667 (Settings → Imports/Exports to generate one). The API is a bit under-documented currently, but feel free to use it.

1 Like

I hate to ask for help on this because this is 100% a problem with my code, not with ttw…
This is my exact code (only the authorization token is missing), and I get back an “Invalid JSON” response. Could you clarify whether that means the json is syntactically wrong, or the semantics are wrong, or I’m using requests wrong?

Stuff is hardcoded just to have the example be exactly what’s not working.

j = {
    "pings": [
            "time": 1618991418,
            "tags": "appena",
            "interval": 1800,
            "last_change": 1619024996154,
        json = j,
        headers = {
            "Authorization": "Bearer ttwprivate_api!xxx",

I’ll mention that looking at the requests that are made from my browser’s console, I can’t find any difference in the json data.

well that’s weird, adding "content-type": "text/plain;charset=UTF-8" to the headers worked. I am… confused. But I’ll take it.

1 Like

That’s… weird. But I checked the library I used for parsing bodies, and it turns out it ignores bodies with certain MIME types by default. I’ve added a note to the API documentation and will see if I can fix it.