Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System notification via event notification system #3429

Closed
wants to merge 8 commits into from
Closed

Conversation

elshize
Copy link
Member

@elshize elshize commented Jun 4, 2022

Introduces devel_new_mail_command config option. When used instead of
new_mail_command, Neomutt's event notification system will be used for
new mail notifications. This is part of a bigger refactoring of the
legacy notification approach. The initial implementation uses only very
limited set of expandos.

@elshize elshize requested a review from a team as a code owner June 4, 2022 19:19
@elshize elshize self-assigned this Jun 4, 2022
@elshize
Copy link
Member Author

elshize commented Jun 4, 2022

@flatcap this is my first attempt at making this work. Looks to be working, but let me know if this is the right direction. I'll try to add some tests soon.

Also, can you help me figure out how to document it in the manual? new_mail_command doesn't seem to be included as option so it's not obvious to me where the new one should be and what it should say.

@flatcap flatcap added type:discuss Your views/opinions are requested type:enhancement Feature Request labels Jun 5, 2022
@flatcap flatcap marked this pull request as draft June 5, 2022 09:00
@flatcap
Copy link
Member

flatcap commented Jun 5, 2022

if this is the right direction

Yep. This a great start! Well done!

how to document it in the manual?

docs/config.c

Some format strings refer to $status_format, etc.

neomutt/docs/config.c

Lines 2575 to 2581 in d5b865c

{ "new_mail_command", DT_COMMAND, 0 },
/*
** .pp
** If \fIset\fP, NeoMutt will call this command after a new message is received.
** See the $$status_format documentation for the values that can be formatted
** into this command.
*/

Some others explicitly list their expandos:

neomutt/docs/config.c

Lines 2521 to 2536 in d5b865c

{ "mix_entry_format", DT_STRING, "%4n %c %-16s %a" },
/*
** .pp
** This variable describes the format of a remailer line on the mixmaster
** chain selection screen. The following \fCprintf(3)\fP-like sequences are
** supported:
** .dl
** .dt %a .dd The remailer's e-mail address
** .dt %c .dd Remailer capabilities
** .dt %n .dd The running number on the menu
** .dt %s .dd The remailer's short name
** .dt %>X .dd right justify the rest of the string and pad with character "X"
** .dt %|X .dd pad to the end of the line with character "X"
** .dt %*X .dd soft-fill with character "X" as pad
** .de
*/


Note: I've made the Pull Request into a "draft" while you develop -- it reduces the load on GitHub

main.c Show resolved Hide resolved
@elshize
Copy link
Member Author

elshize commented Jun 5, 2022

Note: I've made the Pull Request into a "draft" while you develop -- it reduces the load on GitHub

Thanks, need to remember this for the next time.

@elshize elshize force-pushed the michal/notify branch 3 times, most recently from de9641b to 1976ce5 Compare June 6, 2022 00:19
@elshize
Copy link
Member Author

elshize commented Jun 6, 2022

Added some changes.

I was wondering, would it be correct to check for new (and not notified) messages using Emails received value? Say, we would store time of last notification, and then we can verify which messages we haven't notified about? Are the messages added in order so that this could be quickly checked? Or do you have a better approach in mind?

@flatcap
Copy link
Member

flatcap commented Jun 6, 2022

Added some changes

Yes. And then some :-)

would it be correct to check for new messages using Emails received value?

Hmm... I hadn't thought of that.
(/me checks the code...)
I'm not convinced it would always be reliable, but it's probably good enough for the next step.

we would store time of last notification, and then we can verify which messages we haven't notified about?

Yes, so we'll need some storage for that data.
static globals in new_mail.c would do...

Or we could have a new mail struct in main() that's passed to the observer
(and cleaned up on exit)

Are the messages added in order so that this could be quickly checked?

No. This is the big as-yet-unresolved problem in NeoMutt.

The GUI and the backends share an array of Emails.
The Emails are sorted in the user's preferred order.

Every so often, the event loop in the Index or Pager will call mailbox_check().
This will call the backend's mbox_check_stats()

For efficiency, the Emails now get resorted:

  • Maildir by their inode number
  • Imap by their MSN (sequence number)

The new Emails are added to the end of the array.
Then, so the user doesn't get upset, the backend uses the user's $sort, etc to restore the order.
(This is why the cursor sometimes gets lost when new mail arrives)

Or do you have a better approach in mind?

Yes, but I'm not expecting you to implement it :-)

My plan is that the GUI and backends both have an array of Emails.
The GUI's sorted in the user's preferred manner, the backend's in whichever order is best for it.

When new mail arrives, the backend knows which Emails are new (and updates its array).
It sends a notification of: New Mail (Mailbox, EmailArray).
The GUI can now insert the new mail into the correct places, without messing up the display.

This fits in nicely with the new-mail command -- it's given just the information it needs.

@flatcap
Copy link
Member

flatcap commented Jun 6, 2022

Next Steps

Quite a lot's changed since yesterday!

The biggest change is that you've moved the code to libmutt (I guess to make the testing build)
libmutt is the library functions that have no dependencies, so new-mail doesn't belong there.

I suggest creating a new library libnewmail -- it's an independent feature after all.
libmixmaster is a good example of a library whose code is entirely conditional:

neomutt/Makefile.autosetup

Lines 542 to 557 in d5b865c

# libmixmaster
@if MIXMASTER
LIBMIXMASTER= libmixmaster.a
LIBMIXMASTEROBJS= mixmaster/chain_data.o mixmaster/dlg_mixmaster.o \
mixmaster/functions.o mixmaster/mixmaster.o \
mixmaster/remailer.o mixmaster/win_chain.o \
mixmaster/win_hosts.o
CLEANFILES+= $(LIBMIXMASTER) $(LIBMIXMASTEROBJS)
ALLOBJS+= $(LIBMIXMASTEROBJS)
$(LIBMIXMASTER): $(PWD)/mixmaster $(LIBMIXMASTEROBJS)
$(AR) cr $@ $(LIBMIXMASTEROBJS)
$(RANLIB) $@
$(PWD)/mixmaster:
$(MKDIR_P) $(PWD)/mixmaster
@endif

Also, can you add a --devel-new-mail configure option at the end of this section:

neomutt/auto.def

Lines 123 to 132 in d5b865c

# Debug options
debug-backtrace=0 => "DEBUG: Enable backtrace support with libunwind"
with-backtrace:path => "Location of libunwind"
debug-color=0 => "DEBUG: Enable Color dump"
debug-email=0 => "DEBUG: Enable Email dump"
debug-graphviz=0 => "DEBUG: Enable Graphviz dump"
debug-notify=0 => "DEBUG: Enable Notifications dump"
debug-parse-test=0 => "DEBUG: Enable 'neomutt -T' for config testing"
debug-queue=0 => "DEBUG: Enable TAILQ debugging"
debug-window=0 => "DEBUG: Enable windows dump"

You'll need an entry, here:

neomutt/auto.def

Lines 154 to 163 in d5b865c

foreach opt {
asan autocrypt bdb coverage debug-backtrace debug-color debug-email
debug-graphviz debug-notify debug-parse-test debug-queue debug-window doc
everything fmemopen full-doc fuzzing gdbm gnutls gpgme gss homespool idn
idn2 include-path-in-cflags inotify kyotocabinet lmdb locales-fix lua lz4
mixmaster nls notmuch pcre2 pgp pkgconf qdbm rocksdb sasl smime sqlite ssl
testing tdb tokyocabinet zlib zstd
} {
define want-$opt [opt-bool $opt]
}

And you'll want to make your own copy of:

neomutt/auto.def

Lines 1219 to 1222 in d5b865c

# Graphviz dump
if {[get-define want-debug-graphviz]} {
define USE_DEBUG_GRAPHVIZ 1
}

Then you can wrap your code in #ifdefs.

@elshize
Copy link
Member Author

elshize commented Jun 6, 2022

The Emails are sorted in the user's preferred order.

Hmm, that complicates things, checking each time for which messages are new could be terribly inefficient.

I suppose this could be fixed by sending notification event in the backends. I haven't looked at that code very closely, but I'm assuming there must be a moment in time when the backend knows when it's discovering a new message.

There's some other questions to consider, that I'm now just thinking of:

  1. What is the desired behavior for when the application starts? In particular, should it notify about messages that were discovered earlier but still marked as new? (not sure how that translates to IMAP)
  2. When a new message comes in and we notify, do we notify about that message only or aggregate all messages that haven't yet been seen in that particular folder?
  3. Currently, notifications will be triggered for any folder, not only the inbox. How do we want it to work? I suppose it would be good to be able to configure it, right? Not sure about the default though.

@elshize
Copy link
Member Author

elshize commented Jun 6, 2022

The biggest change is that you've moved the code to libmutt (I guess to make the testing build)
libmutt is the library functions that have no dependencies, so new-mail doesn't belong there.

Yes, I moved it to test. I'll create new lib as suggested, makes sense.

Btw, I actually wanted to test a bit more functionality, including the expandos, but I see it's not that easy -- I don't suppose we can link that code currently. And by the look of it, it's not trivial to move to a lib (at least not on its own). I suspect this is a known limitation? Any plans to restructure the code to allow for better testing in the future?

@flatcap
Copy link
Member

flatcap commented Jun 6, 2022

checking each time for which messages are new could be terribly inefficient

All the structures are in-memory, so it probably won't be that slow.
(and this will be a temporary step in development, so I don't think we need worry)

I suppose this could be fixed by sending notification event in the backends.

Yes, that's where we need to go, but your Email.received idea is a simpler step forward.
We can extend EventMailbox to hold an EmailArray and test passing it to new-mail.

neomutt/core/mailbox.h

Lines 186 to 189 in d5b865c

struct EventMailbox
{
struct Mailbox *mailbox; ///< The Mailbox this Event relates to
};

there must be a moment in time when the backend knows when it's discovering a new message

Yes, somewhere in maildir_read_dir() and maildir_delayed_parsing().
I don't know IMAP as well.

  1. What is the desired behavior for when the application starts?

Not sure.

The new-mail command is to notify you when NeoMutt is unattended.
Will NeoMutt always be started by a human, who'll see the new mail?
Or might it be started automatically on login, so the user would want to know about new mail.

Sounds like a config option, but not yet :-)

  1. When a new message comes in and we notify,
    do we notify about that message only or aggregate all messages that haven't yet been seen in that particular folder?

We could have expandos for both and let the user decide:

  • %n - new mails since last notification
  • %Cn - cumulative new emails

This means we'd need to keep track.

  1. Currently, notifications will be triggered for any folder, not only the inbox

Upstream added a couple of flags to the mailboxes command:

  • -poll / -nopoll
    Enable/disable checking for new mail
  • -notify / -nonotify
    Enable/disable notifications of new mail

Implementing them would probably be enough control.

@flatcap
Copy link
Member

flatcap commented Jun 6, 2022

I actually wanted to test a bit more functionality, including the expandos, but I see it's not that easy

hehe, yeah, it's a ****ing nightmare!

I don't suppose we can link that code currently.
I suspect this is a known limitation?

Correct.
Because the expandos are used everywhere, they have deps on everything.

Any plans to restructure the code to allow for better testing in the future?

I created a proof-of-concept for fixing the expandos: https://github.com/neomutt/test-expando

Like my refactoring of the <function>s, it breaks up mutt_expando_format() using callbacks.

https://github.com/neomutt/test-expando/blob/feeda2d3dce2fbd479421d6660364f54be49a863/split-expando.c#L129-L133

Implementing that would be hard, but would make a lot tricky code, testable.

@elshize
Copy link
Member Author

elshize commented Jun 7, 2022

All the structures are in-memory, so it probably won't be that slow.
(and this will be a temporary step in development, so I don't think we need worry)

I suppose. Feels wasteful, but I get your point about a temporary solution.

We could have expandos for both and let the user decide:

  • %n - new mails since last notification
  • %Cn - cumulative new emails

Makes sense.

Upstream added a couple of flags to the mailboxes command

Ok, I'll look into that possibly after this PR.

Implementing that would be hard, but would make a lot tricky code, testable.

Yep, from my very preliminary look at the code, it looks non-trivial.
But possibly worth the while at some point.

BTW. I've been testing this build, and I ran into a problem where I kept
getting the same notification over and over again when switching between
accounts. So there's clearly something off; I'll try to debug.

@elshize
Copy link
Member Author

elshize commented Jun 8, 2022

@flatcap I created a new optional lib, but I think I'm doing something wrong. I don't seem to compile my tests when i define the flag, though ./configure output looks like the variable should be defined... or else I'm misreading that.

Is there anything obvious I'm doing wrong?

@flatcap
Copy link
Member

flatcap commented Jun 8, 2022

Rebased and fixed (minor stuff, see commit messages for details).
Feel free to squash them all into your commit, once you've read them.

The one thing you were missing was the hardest to find...
Not all defines in auto.def are exported.
This meant that DEVEL_NEW_MAIL wasn't defined in config.h

neomutt/auto.def

Lines 1300 to 1305 in 0cf1981

###############################################################################
# Definitions that are going to be substituted in Makefiles and config.h
set auto_rep {
_*
*_TARGETS
BINDIR

Rather than export DEVEL_*, I renamed the symbol to USE_DEVEL_... (to match the others).

@elshize
Copy link
Member Author

elshize commented Jun 9, 2022

Ah, thanks for giving it a look!

@elshize
Copy link
Member Author

elshize commented Jun 12, 2022

@flatcap I was just looking into storing last time of notification... these must be per mailbox, right? to work correctly...

The question is: how to store these? Should I create some structure on the newmail side or another field in the Mailbox struct? The latter seems much easier, though not sure if that's good design. Either way, any pointers as to where in the code I would initialize that new field? I have a hard time finding it.

Copy link
Member

@flatcap flatcap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the first checkpoint should be using realistic notifications.

docs/config.c Show resolved Hide resolved
mutt_config.c Show resolved Hide resolved
const char *if_str, const char *else_str,
intptr_t data, MuttFormatFlags flags);
int handle_new_mail_event(const char *cmd, struct NotifyCallback *nc, Execute *execute);
int new_mail_observer(struct NotifyCallback *nc);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only new_mail_observer() is used from the outside, so we can drop everything else.
Including the #includes. They could be replaced with a forward declaration:

struct NotifyCallback;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually not entirely true. I am using those functions in tests. I split them to be able to write some unit tests. If I merge all of them together, it will be very difficult to write any tests. I suppose you can hack it and set config to use some specific command that writes to a file and we then read it to verify... Would you prefer I did that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! I always forget the tests.
Leave them as they are and I'll re-educate myself on their usage :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure; if you see a better way to test these, let me know.

if (!c_devel_new_mail_command)
return 0;
return handle_new_mail_event(c_devel_new_mail_command, nc, execute_cmd);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I'd merge handle_new_mail_event() and execute_cmd() into new_mail_observer().
We don't need the extra levels of indirection or swap-out-able-ness, yet.

mx.c Outdated
struct EventMailbox ev_m = { m };
notify_send(m->notify, NT_MAILBOX, NT_MAILBOX_NEW_MAIL, &ev_m);
m->notified = true;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want this to be a step up from the old feature...

I suggest a simple mechanism to start:

  • Create a static time_t in this file that stores the time of the last check
  • The first time through this code, (t == 0), we update the time to now, but don't send a notification
  • Next time, we search the Mailbox for any Emails whose received time is after t and put them in an array
  • Send a notification with the Mailbox and the EmailArray

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you say EmailArray do you mean an existing type? I do see MdEmailArray but that seems to be something different. Should I define a similar one for the purpose of the notifications?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EmailArray do you mean an existing type?

No, it'll be a new type using ARRAY (which works like a C++ template).
We want an ARRAY of struct Email * (similar to MdEmailArray).
As it will be general-purpose, we can put it at the end of the struct Email definition.

(Note: ARRAY supports creating arrays of objects, e.g. AliasViewArray)

We'll use the ARRAY as a temporary collection.
Since the Emails will be owned by the Mailbox we only need to ARRAY_FREE() afterwards.

We already do something similar in the Index, op_pipe().
Unfortunately, that was written before we'd invented ARRAY.

neomutt/index/functions.c

Lines 1763 to 1766 in ef0cb55

struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
el_add_tagged(&el, shared->mailboxview, shared->email, priv->tag);
mutt_pipe_message(shared->mailbox, &el);
emaillist_clear(&el);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks for clarifying.

@flatcap
Copy link
Member

flatcap commented Jul 1, 2022

IRC: how to iterate throught the messages in a Mailbox?

Yes, msg_count is the right one.
The comments are a bit misleading; I'll fix them.

  • emails[] C array of every Email
  • email_max size of emails array
  • msg_count number of Emails in emails[]

There's also a "virtual" array, v2r[] which is used for display.
If the Email threads are collapsed, or the Index is <limit>ed, then this will container fewer entries than emails

  • v2r[] C array of ints (indexes into emails)
  • vcount size of v2r[]

@elshize
Copy link
Member Author

elshize commented Jul 1, 2022

Thanks for the explanation. I pushed some changes, and I started testing it after changes to see if it works as expected.

I guess there's still a question of expandos to use. I've been using some random characters I came up with, but let me know if you'd like me to change these.

I know you mentioned something about a two-character %Cn but I'm not sure how to implement that, the function takes only one character as op. So if that's something you'd like, I would appreciate a hint as to how to do this.

@elshize
Copy link
Member Author

elshize commented Jul 1, 2022

I must be missing some important point here... I kept getting a segfault on access to an email, so I debugged it, and I see some of those emails in range 0 to msg_count are strange, not valid memory according to gdb, hence the segfault.

Not to mention that some of them are actually NULL, which is unexpected to me as well.

What am I missing?

@flatcap
Copy link
Member

flatcap commented Jul 2, 2022

I've been using some random characters

That's fine for now

about a two-character %Cn but I'm not sure how to implement that

It's a bit of a cheat. op shows the 'key' character and we have look at src[0] for the second.
Here's where the Index supports %zs, %zc, %zt

neomutt/hdrline.c

Lines 1220 to 1221 in ef0cb55

case 'z':
if (src[0] == 's') /* status: deleted/new/old/replied */

@flatcap
Copy link
Member

flatcap commented Jul 2, 2022

I kept getting a segfault on access to an email
not valid memory according to gdb

Hmm... don't know, they should be valid

Not to mention that some of them are actually NULL

Hmm... I nearly mentioned this.
We used to have to check for NULL.
Empty entries were a side-effect of some functions,
but I thought we were past all that.

What am I missing?

I'll give it a try to see if I can figure it out.

@flatcap
Copy link
Member

flatcap commented Jul 2, 2022

I forgot to mention a "feature" of ARRAYs.
ARRAY_GET() doesn't give you back the pointer you put in, rather a double pointer.

  struct Email *e = email_new();
  ARRAY_ADD(ea, e);
  // ...
  struct Email **ptr = ARRAY_GET(ea, 0);
  struct Email *e2 = *ptr;

This seems like a pain, but it's a feature.
It allows you to distinguish between an ARRAY element that's NULL and an out-of-bounds request.

  if (!ptr) // array index was out of bounds

  struct Email *e = *ptr;
  if (!e) // array element is `NULL`

@elshize
Copy link
Member Author

elshize commented Jul 2, 2022

I am at a loss here. Here's my test: (1) starting with empty gmail Inbox, (2) sending an email, (3) running neomutt with some debug messages printing: msg_count, msg_unread, and pointer values of all of those messages.

I get the correct number of messages and unread messages, but all of the pointers are NULL.

Here's the function I use for this test:

void find_new_emails(const struct Mailbox *mailbox, time_t last_notified,
                     struct EmailArray *emails)
{
  int i = 0;
  mutt_debug(LL_DEBUG1, "Mailbox: %s\n", mailbox_path(mailbox));
  mutt_debug(LL_DEBUG1, "MSG COUNT: %d\n", mailbox->msg_count);
  mutt_debug(LL_DEBUG1, "Unread: %d\n", mailbox->msg_unread);
  for (; i < mailbox->msg_count; i++)
  {
    struct Email* email = mailbox->emails[i];
    mutt_debug(LL_DEBUG1, "msg(%d): %d\n", i, email);
  }
}

And here's the output:

[2022-07-02 11:02:58]<1> find_new_emails() Mailbox: /home/elshize/.mail/gmail/Inbox
[2022-07-02 11:02:58]<1> find_new_emails() MSG COUNT: 6
[2022-07-02 11:02:58]<1> find_new_emails() Unread: 5
[2022-07-02 11:02:58]<1> find_new_emails() msg(0): 0
[2022-07-02 11:02:58]<1> find_new_emails() msg(1): 0
[2022-07-02 11:02:58]<1> find_new_emails() msg(2): 0
[2022-07-02 11:02:58]<1> find_new_emails() msg(3): 0
[2022-07-02 11:02:58]<1> find_new_emails() msg(4): 0
[2022-07-02 11:02:58]<1> find_new_emails() msg(5): 0

No matter what I do: go to that inbox, read some emails, etc. -- just all are zeroes.

I am perplexed. No idea what I'm doing wrong here.

@flatcap
Copy link
Member

flatcap commented Jul 4, 2022

I've just made some small tweaks...

  • rebased
  • squashed a build fix (so all commits build)
  • renamed global to LastNotified
  • moved the callback to mx_mbox_check()

With the callback in mx_mbox_check_stats(), I couldn't trigger it.
Now it's in mx_mbox_check() it seems to get called reliably.
I didn't manage to repeat your problem; Mailbox->emails[] seems to be populated.

@elshize
Copy link
Member Author

elshize commented Jul 9, 2022

@flatcap I made some changes, and I think this is currently working as I expect it to. I'm running it for my own workflow to find any potential issues.

A few comments:

I found that defining two additional fields for a mailbox simplifies the code a lot, so I went for that. We can always consider refactoring that, but at the moment, it's the cleanest approach, I think.

You'll notice that I update the unnotified counter (btw. can you think of a better name?) in a different place and under different conditions than msg_new. I've struggled with figuring out what was wrong, but then I noticed that msg_new is not what I expected, though not sure if by design or not. Maybe I'm missing something, but it seems to me that this count is never higher than 1 because it changes that one bool variable when it finds the first one. And this is how it worked when testing.

I wasn't sure how to approach unread emails count with regards to maildir_check_cur. If set, then this will work as expected, but if not set, then %u will expand to the number of unread messages in new/ directory. I suppose this is ok behavior, but just want to make it clear.

I'm setting the initial time to 0, which means we'll be notified on startup about any new messages. I gave it some thought and I think this actually makes sense, especially for maildir, where new emails can come without neomutt being open. So it's not such a bad idea to notify if there are any. Though ideally, we'd probably want a config option for that to allow the user to decide what they want. What do you think?

Finally, as I said before, I only focused on maildir. I think we should merge maildir first; the question is whether to continue working on maildir functionality, or first implement the existing functionality for other backends?

@flatcap flatcap force-pushed the michal/notify branch 2 times, most recently from be9aee4 to 758d1eb Compare March 23, 2023 08:40
@flatcap flatcap force-pushed the michal/notify branch 2 times, most recently from a022aea to 5dfc1a0 Compare April 14, 2023 22:29
elshize and others added 8 commits April 15, 2023 00:38
Introduces `devel_new_mail_command` config option. When used instead of
`new_mail_command`, Neomutt's event notification system will be used for
new mail notifications. This is part of a bigger refactoring of the
legacy notification approach. The initial implementation uses only very
limited set of expandos.
Rename to match library dir
Not all defines in auto.def are exported.
See auto.def:1300
@flatcap
Copy link
Member

flatcap commented Apr 24, 2023

@flatcap flatcap closed this Apr 24, 2023
@flatcap flatcap deleted the michal/notify branch April 24, 2023 20:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:discuss Your views/opinions are requested type:enhancement Feature Request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants