Saturday, December 11, 2021

srf - spaced repetition flashcards

I have been using srf to study Mandarin for about 6 months now, with a selection of decks imported from anki, and cards I have created myself. 

My study time is better regulated and I am making better progress than when I was studying with anki

The difference is the scheduling algorithm. The scheduler in srf regulates new cards automatically, to maintain a target study time per day and sorts due cards differently, allowing good progress even with a large backlog of due cards (e.g. after not studying for a few days).

Initial development of srf is now complete. Rate of change is slowed. The user interface, particularly for editing content, remains a bit crude but adequate. Lately, I spend most of my time studying and very little developing the program.

I was reluctant to develop a new program. It took a lot of time away from study. But the limitations of the anki scheduler were too frustrating and it is now clear that a better scheduler makes a big difference to progress. From a technical perspective, the differences are subtle and simple but practically they make a big difference.

The essential differences are:

  • present cards with shorter interval before cards with longer interval
  • automatically regulate new cards based on past and future workload

Presenting cards with shorter intervals first makes a big difference when working through a backlog. Anki presents cards in the order they were due, regardless of interval. The difference is subtle but important.

With a large backlog in Anki, the backlog effectively blocks review of cards with shorter intervals. Unfamiliar / difficult cards are not seen as soon as they should be, defeating learning. With a large enough backlog, it is practically impossible to learn: one churns through a large number of cards without making progress because it is too long between reviews.

The problem is exacerbated by anki introducing more new cards every day, despite the backlog. It is possible to change the number of new cards per day manually but paying attention to this takes attention away from studying.

The scheduler in srf prioritizes the cards you are learning (with shorter intervals) over the backlogged cards. Thus, one can learn despite the backlog and that learning is the way to clear the backlog. And while there is an excessive backlog, srf does not introduce new cards, so gradually, reliably, the backlog is cleared. When it is cleared, new cards are introduced again.

With these changes, study time per day is more consistent: varying closely around configured target study time per day. And progress is better when one is not overloaded.

libinput - touchpad acceleration - piecewise linear profile

I have implemented a piecewise linear acceleration profile for touchpad in a fork of libinput.

The mapping function is defined by an array of points: (speed, factor), which must be sorted in order of increasing speed. Between the points, factor is determined by linear interpolation.

A single point gives a fixed acceleration factor, like the libinput flat profile.

Two points give a single, linear slope, clipped at the upper and lower input speeds. But the clipping is irrelevant if the speeds of the two points are sufficiently low (e.g. 0) and high respectively, in which case all inputs will be at speeds between the points.

Factor
^
|                   _________
|                 /
|                /
|               /
|-----------/
|
+-----------------------------------------------> Speed

More complex curves can be approximated by more points. With enough points, any of the profiles in the old X.org server can be approximated.

At the moment, configuration parameters are hard coded, because I haven't figured out how to add configuration parameters and because using new parameters would require changing code outside libinput (e.g. the X or Wayland libinput drivers).

One of the limitations of libinput is that the only means of configuration is via the API. As a result, to add new configuration parameters requires modification of both the libinput library and whatever software (e.g. Wayland compositor, xf86-input-libinput, etc.) calls it. I may add configuration via environment variable to work around this limitation but in the meantime, as you will have to compile and install this version anyway, it is almost as easy to edit the source as a separate configuration file or environment variable.

See the touchpad-pl branch of this fork of libinput.

To try this profile for your touchpad:

$ git clone https://github.com/ig3/libinput.git
$ cd libinput
$ git checkout touchpad-pl
$ meson --prefix=/usr builddir/
$ ninja -C builddir/
$ sudu ninja -C builddir/ install

This is working for me on Debian 11 with xfce desktop. I haven't tried a system running Wayland.

To change the profile, edit src/filter-touchpad-pl.c. The parameters are in function touchpad_accel_profile().

To test:

$ sudu libinput debug-events

Monday, November 29, 2021

Brabantia BBEK1114 Break Maker

The manual for this bread maker is not very good and the functionality is limited. If I were buying another, I would look for something that lets me start at any point in the programs instead of only being able to start at the beginning.

I mostly used the Sweet Bread and French Bread programs. Timing isn't in the manual, which makes it difficult to add nuts, etc. as the bell is not very loud: unless you are right by the machine and listening for it, it is easy to miss.

The timer counts down. If you add time (up to 15 hours total) the additional time is added at the beginning, doing nothing. The program itself is not changed otherwise. This is a problem because all the ingredients are in the machine and the yeast can start fermenting. It would be better to do an initial mix and beat down the dough periodically, instead of leaving it to sit un-mixed. But there is no such option. Best one can do is leave it in a cool place so the dough doesn't rise too much and overflow. 

There is no indication in the manual of the baking temperature. I haven't tried to measure it.

I generally use only a teaspoon or less of dry yeast and about half the indicated sugar in the 750g recipe and it rises to fill the machine, sometimes rising to press against the top. I haven't tried a 1kg recipe but expect it would overflow if I didn't do anything to limit its rising.

I have only made a few of the recipes. The results are surprisingly good and it is very easy, but more like cake than bread. Quite short and crumby. Adding some gluten to the 11.5% protein flour helps a bit.

Sweet Bread

  • 3:45 - Start / Mix
  • 3:41 - Knead
  • 3:38 - Rest
  • 3:30 - Knead
  • 3:20 - Rest
  • 3:10 - Knead
  • 2:55 - Rest
  • 2:40 - Knead
  • 2:35 - Add / Knead
  • 2:25 - Ferment
  • 1:35 - Beat down
  • 1:34 - Ferment
  • 0:50 - Bake
  • 0:00 - Keep warm

French Bread

  • 4:00 - Start / Mix
  • 3:56 - Knead
  • 3:50 - Rest
  • 3:40 - Kenad
  • 3:35 - Rest
  • 3:30 - Knead
  • 3:10 - Rest
  • 2:55 - Knead
  • 2:50 - Add / Knead
  • 2:40 - Ferment
  • 1:40 - Beat down
  • 1:39 - Ferment
  • 0:50 - Bake
  • 0:00 - Keep warm

Monday, November 15, 2021

Libinput: using libinput as a library

The libinput documentation includes a section on using libinput as a library, but with no prior experience with libinput, I found it inadequate to get me oriented. So, here are some additional notes.

Background

libinput is a library to handle input devices in Wayland compositors and to provide a generic X.Org input driver. It provides device detection, device handling, input device event processing and abstraction so minimize the amount of custom input code compositors need to provide the common set of functionality that users expect. Essentially, it is an interface to the input devices.

libinput is not used directly by Wayland applications, it is an input stack used by the compositor.

libinput is not used directly by X applications but rather through the custom xf86-input-libinput driver.  

Libinput may be used by programs other than these display servers. For example, the libinput distribution provides several tools for testing and interacting with libinput. Any program that accesses the input devices supported by libinput may use libinput to access them, as an alternative to accessing them directly.

libinput is designed to handle all input devices available on a system but it is possible to limit which devices libinput has access to. For example, the use of xf86-input-libinput depends on xorg.conf snippets for specific devices. But libinput works best if it handles all input devices as this allows for smarter handling of features that affect multiple devices.

libinput handles all common devices used to interact with a desktop system. This includes mice, keyboards, touchscreens, touchpads and graphics tablets. libinput does not expose the device type to the caller, it solely provides capabilities and the attached features (see this blog post).

libinput does not handle some devices. The primary reason is that these device have no clear interaction with a desktop environment.

See Building against libinput for guidance on configuration and linking the libinput library. 

Libinput does not have many configuration options. This is deliberate.

Initializing a context

The first step in using libinput is initializing a context. The context is manifest as a data structure. Most calls to libinput functions require this context as an argument.

The libinput API documentation has a section on Initialization and manipulation of libinput contexts.

Saturday, November 13, 2021

Libinput sample

The libinput API documentation has a sample program on the main page but it doesn't compile as given.

This revision of it does compile and run:

#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <libinput.h>
#include <libudev.h>
#include <stdio.h>

#define ANSI_HIGHLIGHT    "\x1B[0;1;39m"
#define ANSI_RED    "\x1B[0;31m"
#define ANSI_GREEN    "\x1B[0;32m"
#define ANSI_YELLOW   "\x1B[0;33m"
#define ANSI_BLUE   "\x1B[0;34m"
#define ANSI_MAGENTA    "\x1B[0;35m"
#define ANSI_CYAN   "\x1B[0;36m"
#define ANSI_BRIGHT_RED   "\x1B[0;31;1m"
#define ANSI_BRIGHT_GREEN "\x1B[0;32;1m"
#define ANSI_BRIGHT_YELLOW  "\x1B[0;33;1m"
#define ANSI_BRIGHT_BLUE  "\x1B[0;34;1m"
#define ANSI_BRIGHT_MAGENTA "\x1B[0;35;1m"
#define ANSI_BRIGHT_CYAN  "\x1B[0;36;1m"
#define ANSI_NORMAL   "\x1B[0m"


static int open_restricted(const char *path, int flags, void *user_data)
{
        int fd = open(path, flags);
        return fd < 0 ? -errno : fd;
}
 
static void close_restricted(int fd, void *user_data)
{
        close(fd);
}
 
const static struct libinput_interface interface = {
        .open_restricted = open_restricted,
        .close_restricted = close_restricted,
};

static void
log_handler(struct libinput *li,
    enum libinput_log_priority priority,
    const char *format,
    va_list args)
{
  static int is_tty = -1;

  if (is_tty == -1)
    is_tty = isatty(STDOUT_FILENO);

  if (is_tty) {
    if (priority >= LIBINPUT_LOG_PRIORITY_ERROR)
      printf(ANSI_RED);
    else if (priority >= LIBINPUT_LOG_PRIORITY_INFO)
      printf(ANSI_HIGHLIGHT);
  }

  vprintf(format, args);

  if (is_tty && priority >= LIBINPUT_LOG_PRIORITY_INFO)
    printf(ANSI_NORMAL);
}
 
 
int main(void) {
  fprintf(stderr, "start main\n");
        struct libinput *li;
        struct libinput_event *event;

        struct udev *udev = udev_new();

        if (!udev) {
          fprintf(stderr, "Failed to initialize udev\n");
          return -1;
        }
 
        fprintf(stderr, "create libinput context for udev\n");
        li = libinput_udev_create_context(&interface, NULL, udev);
        if (!li) {
          fprintf(stderr, "Failed to create libinput context\n");
          return -1;
        }

        libinput_log_set_handler(li, log_handler);
        libinput_log_set_priority(li, LIBINPUT_LOG_PRIORITY_DEBUG);

        if (libinput_udev_assign_seat(li, "seat0")) {
          fprintf(stderr, "Failed to assign seat\n");
          libinput_unref(li);
          li = NULL;
          udev_unref(udev);
          return -1;
        }

        libinput_dispatch(li);
 
        while ((event = libinput_get_event(li)) != NULL) {
 
                // handle the event here
                fprintf(stderr, "in the event loop\n");
 
                libinput_event_destroy(event);
                libinput_dispatch(li);
        }
 
        fprintf(stderr, "unreference libinput context\n");
        libinput_unref(li);
 
        return 0;
}

 

This can be compiled with:

$  gcc -o sample sample.c `pkg-config --cflags --libs libinput` -ludev

 

Tuesday, November 9, 2021

Libinput - alternate acceleration profile for touchpad

I don't like the libinput acceleration profile for my laptop touchpad.

I have Debian 11 (bullseye) on my laptop with xfce4 desktop. It defaults to libinput for the touchpad but I didn't like the acceleration profile. It was either too fast for accurate fine movements or too slow for traversing the display. While I could adjust the speed, I couldn't find any setting that was satisfying. I don't recall when the driver changed but it has been a long time now, maybe since I replace Mint with Debian a year or two ago. In any case, it is long enough that I am not merely suffering from a slight change to the acceleration profile conflicting with my muscle memory: even given plenty of time to adjust, I still don't like the libinput acceleration profile.

Recently, I decided to do something about it. I tried the Synaptics driver and it was better but still not what I wanted. I tried the evdev driver but it didn't work at all: the cursor didn't move no matter what I tried. No error message: just no movement. I really wanted to try some of the acceleration profiles that evdev provides but eventually I gave up.

It seems that the Synaptics and evdev drivers are deprecated and the libinput driver will predominate in the future, so I decided to focus on it. Others have criticized its acceleration profile, with sentiments like mine. The libinput FAQ includes:

Why is libinput’s pointer acceleration worse than synaptics/evdev

This is a known problem affecting some devices and/or use-case but the exact cause is still unknown. It may be a device-specific issue, it may be a bug in libinput’s acceleration code, it may be a disagreement about how pointer acceleration should feel. Unfortunately this is something that affected users need to investigate and analyze.

As I had tried all sorts of settings and given it lots of time to get used to it but was still bothered by it, I decided to try implementing an alternate acceleration profile. I am not hopeful that this will ever be accepted into libinput. For good reason, the developers need to keep it simple and robust with minimal support resources. Adding features adds support burden. But I only care about support for me and my one system, and it is, after all, free software.

It took me a couple of days to learn enough to get started but once I familiarized with the libinput code a bit, it turned out to be easy to implement an alternate acceleration profile with hard coded parameters: just one function to change. Adding configuration parameters is a bigger challenge for another day. But as I am recompiling libinput anyway, changing the parameters in the code isn't a problem, and I don't want to be changing them anyway, once I get them right.

I built libinput per Building. This gave me a local repository.

I created a new branch: wip/alternate_profile.

I modified src/filter-touchpad.c, which contains the 'adaptive' profile acceleration code. I created a new function, touchpad_accel_profile_constrained_linear, modeled after the original touchpad_accel_profile_linear (which is, in fact, not a linear transfer function) that implements the adaptive profile (the default profile) and changed the pointer to the transfer function to the new one. I don't yet know how to add the new profile selectable by configuration but I don't want to use the adaptive profile so replacing it is OK for now. It's a crude hack, but fixes my immediate problem and is a step towards adding a new profile.

The new profile has four hard-coded configuration parameters:

  const double minimum_factor = 0.05; /* unitless */
  const double maximum_factor = 0.75; /* unitless */
  const double lower_threshold = 15.0; /* mm/s */
  const double upper_threshold = 100.0; /* mm/s */

The acceleration factor is a simple function of speed:

   accel
   factor
     ^
     |       --------
     |      /
     |     /
     |----/
     +-------------> speed in


At speeds below lower_threshold, factor is minimum_factor.

At speeds above upper_threshold, factor is maximum_factor.

At speeds between the two thresholds, factor is a linear ramp from minimum_factor to maximum_factor.

This gives me precise control at slow speeds and a gradual transition to rapid movement at high speed. The transition is sufficiently smooth that it feels predictable. I may be wrong, but I think the linear ramp is fine: no need for any fancy curve, as long as the transition from slow to fast isn't too abrupt, i don't think the details of the curve matter. If it were a mouse, I might care about the details of the curve more, but a touchpad is so crude, it doesn't matter.

Otherwise, there are no 'magic' numbers, as there are in touchpad_accel_profile_linear, but there may be elsewhere. I haven't reviewed all the code of libinput, and much less so the Xorg libinput driver or Xorg server themselves.

The parameters are hard coded. There is no provision to set them by Xorg configuration files, xinput, xset or whatever. I don't yet know how to add such capability.

But it is easy enough to recompile and reinstall, so not too difficult to change the parameters.

Of course, installing this modified libinput affects the entire system and there is no provision for per-user configuration at this point. I am using it on my personal laptop, so there are no other users.

I am using this on an old Toshiba Satellite Pro C665. I also added a hwdb entry for it, as the default touchpad dimensions were incorrect. In  /etc/udev/hwdb.d/61-evdev-local.hwdb:

# Toshiba Sattelite Pro C665
evdev:name:SynPS/2 Synaptics TouchPad:dmi:*svnTOSHIBA:*pnSatelliteProC665**
 EVDEV_ABS_00=1122:5863:58
 EVDEV_ABS_01=951:5118:99
 EVDEV_ABS_35=1122:5863:58
 EVDEV_ABS_36=951:5118:99

I followed the instructions at Coordinate ranges for absolute axes. I was surprised to find that hwdb only had entries for two Toshiba systems. You might check your own touchpad to make sure it is correct.

If you are interested, you can clone it from wip/alternate_profile

To build and try the new profile:

  1. $ git clone https://github.com/ig3/libinput.git
  2. $ cd libinput
  3. $ git checkout --track  origin/wip/alternate_profile
  4. $ meson --prefix=/usr builddir/
  5. $ ninja -C builddir/
  6. $ sudo ninja -C builddir/ install
  7. restart your X server (e.g. logout and login or reboot)

I have been using it for a few days now and it feels much more comfortable than the libinput 'adaptive' profile. I have good control at slow speeds and I can easily move the cursor across the screen. In summary, it works as I expected and I like it.

Linux touchpads in Xorg X Server with libinput

I have Debian 11 (bullseye) on my Toshiba Satellite Pro C665 laptop. It uses libinput by default but I found the cursor difficult to control. With a low acceleration I had precise control but it was tedious traversing the screen. With a high acceleration I could traverse the screen OK but precise control of the cursor was difficult to impossible. So, I began to learn far more than I ever wanted to know about Xorg X Server, libinput, synaptic, evdev and pointers in general. I have only learned a little: not yet enough to achieve reasonable behaviour.

My touchpad works with the libinput and synaptic input drivers but doesn't work with evdev. For reasons unknown, when I configure evdev there are no errors logged but the cursor doesn't move. The buttons are recognized but I can't move the cursor. Other's report this behaviour and the most common 'solution' is to use synaptic or libinput drivers. The events are generated. I have no idea why they are ignored when I try the evdev input driver. Maybe evdev is essentially unsupported software recent years and broke at some point and no one cares.

This thread addresses another fault in libinput but it includes instructions for re-building libinput from source. I have now done this both via 'apt build-dep' etc. as indicated there, and also from xf86-input-libinput which I built and installed with no ill effect.

The person who responded with the instructions for for building libinput said, among other things: "I agree that this isn't an ideal user experience. On the other hand, recompiling libinput in 2020 takes about as much time as taking out a credit card from your wallet and paying for another closed source macOS app." and there was some subsequent back and forth among respondents about how reasonable this workaround is.

I have been aware of X Server since the 1980's. I have been using Linux since the mid 90's, written device drivers from scratch, hacked parts of the kernel and built and debugged many packages through the years. I have been searching for a way to improve my touchpad behaviour for two days now and have learned only a little of what software is involved, how it all relates and how it is all configured. Had I not stumbled on the above linked thread, it might have been many more days before I learned how to build libinput from source and much longer to understand what all the code was doing to resolve that problem. I have looked at the code that implements the acceleration profile but it will be a long while before I understand how to add my own profile and configuration options (i.e. changes to processing of the Xorg configuration files, xinput, xset and who knows what other places might be required to make something that works).

The documentation (in all forms I can find, including man pages, tutorials and forum discussions) is typically terse, often using undefined terms and often inconsistent with the current implementation on my laptop. The software and how to configure it has obviously changed through the years and it is difficult to determine what advice applies to what version of the software. Even current Xorg documentation on the official Xorg website is inconsistent with my experience when using the libinput device driver.

My conclusions thus far:

The evdev device driver doesn't work: there are no errors but the cursor doesn't move when I use the touchpad. No idea why. Much of the documentation I can find about setting acceleration profiles seems to be specific to the evdev input device driver. I would like to try some of these profiles but I can't because the cursor doesn't move at all.

The synaptic device driver works but it is difficult to understand how to configure acceleration. Synaptics Pointer Acceleration makes some convincing arguments that the Synaptic driver is complex to the point of being unpredictable, though it might not be so intractable if one cares only about a single hardware configuration. The author is one of the main contributors to both the Synaptic and libinput device drivers, so well informed. After some effort I was unable to achieve what I wanted with this input driver.

The libinput driver is the default on my and many systems. Many complain about the acceleration. Documentation of it is misleading and there are very limited configuration options. I have been unable to configure it to allow my both fine control of the cursor and easy traversal of larger areas. The source code suggests (to my as-yet superficial understanding) that the implementation is more complex than and inconsistent with the documentation, with obscure heuristics determining the acceleration parameters, rather than configuration parameters.

It is a dismaying prospect to have to spend many more days if I want to understand what all the configuration options (across GUI configuration tools, command line tools and various configuration files for Xorg, udev, xfce4, etc., etc., etc.) are and what they do and which part(s) of the software they affect.

Presumably there is code in the hardware device driver, Xorg input driver and Xorg X Server that could and possibly do contribute to the overall mapping from the hardware to the cursor position. But it is a morass of poorly documented components, leading to confusion and every experiment thus far requires an hour or two of researching errors, installing and configuring tools, often to achieve little to nothing.

Saturday, July 10, 2021

srf - Spaced Repetition Flashcards

It is over a month since I started using srf for study instead of Aniki.

I prefer the scheduler in srf. The main reason I wrote it was to implement the scheduler to my preferences. I would have written an Anki add-on to do this if it were possible, but the only way to modify the Anki scheduler is to fork Anki and make pervasive changes. I didn't want to do that. The scheduler in srf is much simpler than that in Anki. Simple is good.

Like Anki, there are four options when a card is reviewed: again, hard, good and easy. Again resets the card to a 10 second interval and sets the factor according to the previous interval (if the previous interval was longer, the factor is larger). Hard reduces the interval by half and reduces the factor by 50. Good increases the interval according to the current factor and increases the factor by 50. Easy increases the interval according to the current factor, with a minimum of one week, and increases the factor by 200.

The factor controls how quickly the interval increases when Good or Easy are selected. The interval increases exponentially at a rate determined by the factor. New interval = old interval * factor / 1000. Factor changes at every view, decreasing for Again or Hard and increasing for Good or Easy. The scheduler hunts for an appropriate factor. The assumption is that different factors are optimum for different cards. But this might all be excess complexity. It probably is and I might simplify this at some point in the future.

Keep in mind that one doesn't study strictly according to the schedule. One studies for a period then takes a break. Perhaps one or a few study sessions per day. Maybe sometimes not studying for several days. Thus the fine details of the scheduling algorithm are not important. What is important is that the interval increases at a reasonable rate if the card is remembered and decreases if it is not remembered.

Again immediately reduces the interval to 10 seconds, which is also the initial interval for a new card. This is very short, but it is expected to increase rapidly. The factor varies according to the previous interval: 1000 if the previous interval is very short, up to 10000 if the previous interval was a year. Thus the rate the interval increases depends on what the previous interval was. Selecting Again twice in a row will reduce the factor to 2000. The expectation is that a card that had reached a long interval before being forgotten probably doesn't need a lot of close repetition: just a few reminders then quickly return to a longer interval.

Hard reduces the interval by half. If a card is repeatedly hard, the interval will decrease exponentially, down to 30 seconds. And the factor is decreased a little (50).

Good increases the interval according to the factor and increases the factor a little (50).

Easy increases the interval as Good does, except with a minimum interval of 7 days, and increases the factor a little faster (200).

If all is well, cards should mostly alternate between Hard and Good. In the early stages of learning, Again might be used, but eventually one should get beyond forgetting a card completely. Easy isn't necessary, but it will save time if a card is in fact very easy in the early stages of learning.

There are no decks in srf, nor any of the complexity that goes along with them. 

The only way to get cards into srf is to import an Anki database.

I hope, before too long, to implement four enhancements to srf:

  1. UI to edit notes
  2. UI to edit cards
  3. import/export of notes and cards, including import for Anki published decks
  4. Tags on notes and/or cards and filter by tag for selective study

But lately, I am busy studying and with other projects, so I may not get to these for a while. I suppose the next big motivation will come when I want to change or add a few notes, then when I have learned the notes/cards I have and want to add more in bulk. But I have 30,000 cards yet to study, so that day may not come for a while yet.

Wednesday, June 2, 2021

Spaced Repetition Flashcards

After two years using Anki, I decided to write my own spaced repetition flashcards software.

I was using the Anki v2 scheduler but it had various faults and limitations, the most serious of which were a fixed number of new cards per day, lack of API to implement an alternate scheduler, frequently changing add-on API and overly complex build environment that requires familiarity with several languages and build tools. 

I created add-ons that overcame some of the faults but it became too difficult to maintain them. And they didn't achieve all that I wanted. Because of how many of the scheduling features are implemented, many problematic aspects are difficult or practically impossible to alter or improve. It became evident that it would be less work and better outcome to write my own software than to continue struggling with Anki.

I use only a small subset of Anki features. Only an single instance of the desktop version (so no syncing) and only simple cards - no cloze cards. The Anki application was vastly more complicated than I needed.

So, I wrote my own. It is browser based with the server running on nodejs. It is simple: less than 1000 lines of JavaScript and a few templates. It's a bit crude yet, but I have been using it to study for over a week now. It presents all my cards as well as Anki did. It has, in my opinion, superior scheduling. The build tools are simple and well known - in other words: easy to install and use. I only need to know one programming language: JavaScript, or five if you include the SQL, Handlebars templates, HTML and CSS as languages. All but the Handlebars templates are ubiquitous and familiar, and Handlebars templates sufficient for this purpose, are simple.

About the same time, I updated my Anki add-on again, to support up to the then current 2.1.43 (and probably a few subsequent - until the internals change again). This is likely to be the last time I will update the add-on as I am not using it or Anki any more. 

Scheduling is simpler and more consistent than Anki. New cards are automatically regulated to maintain a steady workload of about 1 to 2 hours of study per day. The number of cards to be reviewed each day is somewhat random, but if study time in a day (actual or projected) exceeds 1 hour then no new cards are presented. This allows me to focus on the cards I am already learning, until I learn them well enough that cards to be studied / study time fall below the threshold, after which new cards are presented again. Other than the limit on total study time, there is no limit on new cards.

The scheduler prioritizes cards with shorter intervals over cards with longer intervals. This doesn't make much difference when you are up to date but it makes a big difference with a backlog, as I had after working on on this new app for a few days instead of studying. Even with a large backlog, the challenging cards don't get deferred by the backlog. It seems, in this way, to be much better than Anki. A few days after I got back to studying, I had cleared my backlog and was seeing more new cards than I had in a long time with Anki.

I have no research that proves my scheduler is more effective than Anki's but at least it doesn't have the misfeatures that I experienced with Anki: fixed number of new cards per day; excessive complexity of new, learning, re-learning and review cards and queues; a tendency to gross overload and dysfunctional review ordering, resulting in overall failure to learn; adverse interactions of new card limits between nested decks; lack of control of new card order; and limitation of the scheduling algorithm to units of days.

It is based on an import and modification of my old Anki database. I haven't yet written anything to import an Anki (or other) shared deck, but I probably will. However, currently I have about 30,000 new cards, so no need for more any time soon. I wrote an utility to import the Anki database but probably won't use it again: it would be too much of a setback to revert to what I was doing in Anki weeks ago and I don't study with Anki any more. I suppose I might import a new database if I used Anki to download some other decks and wanted to keep multiple databases, but it would probably be better to write an import of shared decks directly than to do it through Anki.

There is a very crude interface for editing notes: just the fields. So, I can correct errors as I find them. I'll add features to add and remove notes at some point.

New card ordering isn't lost when a card is studied. As a result, it is possible to reset a deck to it's original state and start over with the original ordering of new cards. This isn't possible with Anki because the initial ordering is in the due field of the cards, which is overwritten when the card is viewed.

Some of the serialized data in the Anki database is no longer used but there is more to be done. There is nothing fundamentally wrong about storing serialized data structures, but using a non-standard format is bad. I might revise some of the serializations to JSON. Alternatively, I might deserialize them into separate fields in the database. But, for the moment, I have cracked the rust serialization algorithm, so there is no immediate need.

There is room for improvement in the review log, but I can produce basic statistics such as counts of cards studied and due per day, study time per day, number of new cards viewed per day, etc.

Labels