version from 2026 
i started a plan to create a few key apps for this.
- a live chat for end users, so, for each app, they can connect to get real time one on one help how to report bug or how to use it or how to get started as developer or tester
- a packaged app that puts minimized apps to sleep (was linked elsewhere in this wiki)
- an app to point users to source for a package and how to build it and how to submit patches (something gui and interfaces window manager and package manager and git etc)
- an app to report bugs as screenshots or screencasts
currently work is in progress for item 1. a source code and schedule will be posted for it when it is ready. planned ideas for it
- expected workflow
- a web page has schedule for open hour a week
- when open, user joins a chat
- an irc bot auto-invites user to subchannel
- requires a few admin things
- webchat that autojoins on invite
- irc bot that routes users to subchannels
Release 2026_04_06
This includes a basic prototype for live chat support. It includes a webchat that auto-joins on invite and shows the chat schedule, and a irc bot that invites the user to a channel where one on one help will be available. This software can be configured to run live chat support sessions for open source software packages, linux distributions, content and media sites, and so on. In the future it will be programmed to prompt the user for a summary of their inquiry, and to make it easier for volunteer helpers to follow which chats they are actively helping in.
Code Part 1: Edits to the web chat
The web chat https://codeberg.org/emersion/gamja was modified to auto-join users on invite for the user routing to work, and to show chat schedule at time of connection. The chat schedule should be shown more clearly with a countdown but I couldn't get this to work. If you can, please contact me and send me a diff.
diff --git a/components/app.js b/components/app.js
index cd4e04f..fc57193 100644
--- a/components/app.js
+++ b/components/app.js
@@ -173,6 +173,8 @@ export default class App extends Component {
saslExternal: false,
autoconnect: false,
autojoin: [],
+ chat_time: null,
+ chat_days: [],
ping: 0,
},
connectForm: true,
@@ -273,6 +275,12 @@ export default class App extends Component {
if (typeof config.server.autoconnect === "boolean") {
connectParams.autoconnect = config.server.autoconnect;
}
+ if (typeof config.server.chat_time === "string") {
+ connectParams.chat_time = config.server.chat_time;
+ }
+ if (Array.isArray(config.server.chat_days)) {
+ connectParams.chat_days = config.server.chat_days;
+ }
if (config.server.auth === "external") {
connectParams.saslExternal = true;
}
@@ -723,21 +731,21 @@ export default class App extends Component {
}
}
if (msg.command === "INVITE" && client.isMyNick(msg.params[0])) {
- msgUnread = Unread.HIGHLIGHT;
-
+// msgUnread = Unread.HIGHLIGHT;
+//
let channel = msg.params[1];
- let notif = new Notification("Invitation to " + channel, {
- body: msg.prefix.name + " has invited you to " + channel,
- requireInteraction: true,
- tag: "invite,server=" + serverID + ",from=" + msg.prefix.name + ",channel=" + channel,
- actions: [{
- action: "accept",
- title: "Accept",
- }],
- });
- if (notif) {
- notif.addEventListener("click", (event) => {
- if (event.action === "accept") {
+// let notif = new Notification("Invitation to " + channel, {
+// body: msg.prefix.name + " has invited you to " + channel,
+// requireInteraction: true,
+// tag: "invite,server=" + serverID + ",from=" + msg.prefix.name + ",channel=" + channel,
+// actions: [{
+// action: "accept",
+// title: "Accept",
+// }],
+// });
+// if (notif) {
+// notif.addEventListener("click", (event) => {
+// if (event.action === "accept") {
let stored = {
name: bufName,
server: client.params,
@@ -747,12 +755,12 @@ export default class App extends Component {
this.sendReadReceipt(client, stored);
}
this.open(channel, serverID);
- } else {
- // TODO: scroll to message
- this.switchBuffer({ server: serverID, name: bufName });
- }
- });
- }
+// } else {
+// // TODO: scroll to message
+// this.switchBuffer({ server: serverID, name: bufName });
+// }
+// });
+// }
}
// Open a new buffer if the message doesn't come from me or is a
diff --git a/components/connect-form.js b/components/connect-form.js
index ae8a59e..293fd02 100644
--- a/components/connect-form.js
+++ b/components/connect-form.js
@@ -28,6 +28,8 @@ export default class ConnectForm extends Component {
rememberMe: props.params.autoconnect || false,
username: props.params.username || "",
realname: props.params.realname || "",
+ chat_days: props.params.chat_days || "",
+ chat_time: props.params.chat_time || "",
};
}
}
@@ -54,6 +56,8 @@ export default class ConnectForm extends Component {
realname: this.state.realname,
saslPlain: null,
autojoin: [],
+ chat_days: this.state.chat_days,
+ chat_time: this.state.chat_time,
};
if (this.state.password) {
@@ -111,6 +115,18 @@ export default class ConnectForm extends Component {
`;
}
+
+let chat_status = "Unknown"; let schedule = "TBC";
+if (this.props.params.chat_time && this.props.params.chat_days) {
+const now = new Date(), [h, m] = this.props.params.chat_time.split(':').map(Number);
+const days = this.props.params.chat_days.map(d => ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"].indexOf(d.slice(0,3)));
+let start = days.map(d => {
+ let s = new Date(now); s.setDate(now.getDate() + (d + 7 - now.getDay()) % 7);
+ s.setHours(h, m, 0, 0); if (now > s.getTime() + 36e5) s.setDate(s.getDate() + 7); return s;
+}).sort((a, b) => a - b)[0];
+const end = new Date(start.getTime() + 36e5);
+chat_status = `Now: ${now.toLocaleString()} | Start: ${start.toLocaleString()} | End: ${end.toLocaleString()}`;
+}
let auth = null;
if (this.props.auth !== "disabled" && this.props.auth !== "external" && this.props.auth !== "oauth2") {
auth = html`
@@ -148,7 +164,7 @@ export default class ConnectForm extends Component {
return html`