Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,7 @@ Watch for:
- believing `make warnings` covers `tools/snapshot.c`. It builds only `src/`
and `tests/`, so a snapshot scenario that does not compile is caught by
`make snapshots` alone;
- **Profile tabs.** A person's posts screen (`INDIGO_SEARCH_AUTHOR`) takes its tabs, filters and fetch from `wolfram/profile_tab.h`; do not write Indigo's own filter strings. `indigo_search.tab` holds the tab, the header box cycles it (`indigo_app_next_author_tab`), and likes is offered on the signed-in account only.
- believing the host targets cover `src/atproto/session*.c`. Its Wolfram calls all sit behind `#if defined(__3DS__)`, so `make test`, `make warnings` and `make snapshots` compile none of them. A wrong Wolfram signature, a missing include or an undeclared static has passed all three and failed only the cross-build, five times now: `wf_display_facet`, a missing `<limits.h>` for `UINT_MAX`, `wf_agent_get_author_feed_typed`'s `filter` argument, `s_account` (a static that never existed; the agent's own handle comes from `wf_agent_get_handle`), and a missing `<wolfram/feed_gen_typed.h>` for `wf_feedgen_get_feed_generators_typed`. Run `make` for the console before calling a session change done;
- trusting `make warnings` to match the cross-build's diagnostics. The host
sweep compiles at `-O2` but the `make test` binary is `-O1`, and the ARM
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ as the release notes.

## [Unreleased]

### Added

- A person's posts screen has replies, media and (on your own account) likes tabs: tap the header box to move to the next one. The tabs, their filters and the fetch are Wolfram v0.36's `wolfram/profile_tab.h`. ([#67](https://github.com/ewanc26/indigo/pull/67))

## [0.10.0] - 2026-10-07

What has and has not been run, plainly: the host tests pass (4019 checks under AddressSanitizer and UBSan), all 148 snapshots render, and the 3DS build links in CI against Wolfram v0.35.0. The new image attachment (picker, alt text, upload) has been run on the host only, not on an emulator or a real 3DS, and the upload has never reached a server from Indigo. The code moved into smaller files in this release; its layout renders byte-identically to 0.9.0 but the worker thread and touch handling were not re-run on a console.
Expand Down
2 changes: 1 addition & 1 deletion docs/PARITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ An unknown pairing code (a restarted node forgets its pairings) used to be polle
| Notifications, mark as seen | yes | implemented | emulator fetch; seen marking host only |
| Profiles, follow, unfollow | yes | implemented | host |
| Followers, following | yes | implemented, paged | host |
| Profile tabs | yes | partial: a person's posts only (`INDIGO_SEARCH_AUTHOR`) | host; no replies, media or likes tab (#56) |
| Profile tabs | yes | yes: posts, replies, media and likes (likes on the signed-in account only), cycled by tapping the header box on a person's posts (`INDIGO_SEARCH_AUTHOR`) | host tests; not run on a 3DS or Azahar |
| Pinned posts | yes | implemented | host |
| Avatars | yes | implemented | host, and Azahar: fetched as 128 px JPEG thumbnails, decoded and drawn on the timeline; not on hardware |
| Images on posts, replies and quotes, with alt text | yes | implemented for viewing, behind an alt-text setting | host only |
Expand Down
2 changes: 1 addition & 1 deletion mk/host.mk
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ endif
HOST_CJSON_CFLAGS ?= $(shell pkg-config --cflags libcjson 2>/dev/null)
HOST_CJSON_LIBS ?= $(shell pkg-config --libs libcjson 2>/dev/null || echo -lcjson)
HOST_WOLFRAM_INC := -I$(WOLFRAM_ROOT)/include $(HOST_CJSON_CFLAGS)
HOST_WOLFRAM_FILES := src/agent/muted_words.c src/agent/moderation.c src/failure.c src/time.c src/cdn.c src/attach.c src/util.c src/update/ed25519.c \
HOST_WOLFRAM_FILES := src/agent/muted_words.c src/agent/profile_tab.c src/agent/moderation.c src/failure.c src/time.c src/cdn.c src/attach.c src/util.c src/update/ed25519.c \
src/update/signature.c src/update/update_core.c
HOST_WOLFRAM_CXX_FILES := cpp/wolfram/syntax.cpp
HOST_WOLFRAM_OBJS := $(addprefix $(HOST_OUT)/wf_,$(subst /,_,$(HOST_WOLFRAM_FILES:.c=.o))) \
Expand Down
2 changes: 2 additions & 0 deletions src/app/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ void indigo_app_search_failed(indigo_app *app, const char *message);
void indigo_app_open_people(indigo_app *app, indigo_search_kind kind, const char *subject);
/* One person's posts. Reuses the post list that post search fills. */
void indigo_app_open_author_posts(indigo_app *app, const char *actor);
int indigo_app_author_tab_after(const indigo_app *app);
void indigo_app_next_author_tab(indigo_app *app);
/* The signed-in account's curated lists, then one list's members. Both reuse
* the search screen; the members list reuses the actor rows. */
void indigo_app_open_lists(indigo_app *app);
Expand Down
47 changes: 35 additions & 12 deletions src/app/app_results.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

#include "app/app_internal.h"

#include <strings.h>
#include <wolfram/profile_tab.h>

void
indigo_app_begin_sign_in(indigo_app *app, const char *status)
{
Expand Down Expand Up @@ -183,18 +186,38 @@ indigo_app_open_people(indigo_app *app, indigo_search_kind kind, const char *sub
app->request = INDIGO_REQUEST_PEOPLE;
}

void
indigo_app_open_author_posts(indigo_app *app, const char *actor)
{
if (!actor || !actor[0]) {
return;
}
app->screen = INDIGO_SCREEN_SEARCH;
app->search.kind = INDIGO_SEARCH_AUTHOR;
indigo_search_begin_page(&app->search, false);
indigo_copy_utf8(app->search.subject, sizeof app->search.subject, actor);
indigo_copy_utf8(app->request_actor, sizeof app->request_actor, actor);
app->request = INDIGO_REQUEST_AUTHOR_FEED;
static void begin_author_tab(indigo_app *app, const char *actor, int tab) {
app->screen = INDIGO_SCREEN_SEARCH;
app->search.kind = INDIGO_SEARCH_AUTHOR;
indigo_search_begin_page(&app->search, false);
app->search.tab = tab;
indigo_copy_utf8(app->search.subject, sizeof app->search.subject, actor);
indigo_copy_utf8(app->request_actor, sizeof app->request_actor, actor);
app->request = INDIGO_REQUEST_AUTHOR_FEED;
}

void indigo_app_open_author_posts(indigo_app *app, const char *actor) {
if (actor && actor[0]) {
begin_author_tab(app, actor, WF_PROFILE_TAB_POSTS);
}
}

/* The tab after the one on screen. The likes tab is only offered for the
* signed-in account, so the cycle skips it for anyone else. */
int indigo_app_author_tab_after(const indigo_app *app) {
bool self = strcasecmp(app->search.subject, app->signin.account) == 0;

return wf_profile_tab_next(app->search.tab, self);
}

void indigo_app_next_author_tab(indigo_app *app) {
char actor[INDIGO_POST_NAME_MAX];

if (app->search.kind != INDIGO_SEARCH_AUTHOR || app->search.loading) {
return;
}
indigo_copy_utf8(actor, sizeof actor, app->request_actor);
begin_author_tab(app, actor, indigo_app_author_tab_after(app));
}

void
Expand Down
2 changes: 2 additions & 0 deletions src/app/app_screens.c
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ indigo_app_update_search(indigo_app *app, const indigo_input *input)
case INDIGO_ACTION_FIELD_QUERY:
if (indigo_search_is_typed(s)) {
edit_query(app);
} else if (s->kind == INDIGO_SEARCH_AUTHOR) {
indigo_app_next_author_tab(app);
}
break;
case INDIGO_ACTION_AUTHOR:
Expand Down
3 changes: 2 additions & 1 deletion src/app/search.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "app/search.h"

#include <string.h>
#include <wolfram/profile_tab.h>

static void
keep_visible(unsigned *scroll, unsigned selected, unsigned rows)
Expand Down Expand Up @@ -158,7 +159,7 @@ indigo_search_title(const indigo_search *s)
case INDIGO_SEARCH_POSTS:
return "Post search";
case INDIGO_SEARCH_AUTHOR:
return "Posts";
return wf_profile_tab_name(s->tab);
case INDIGO_SEARCH_LISTS:
return "Lists";
case INDIGO_SEARCH_LIST_MEMBERS:
Expand Down
2 changes: 2 additions & 0 deletions src/app/search.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ typedef struct {
* around one person, so they carry the subject instead of a query. */
char query[INDIGO_SEARCH_QUERY_MAX];
indigo_search_kind kind;
/* The profile tab (wf_profile_tab) an INDIGO_SEARCH_AUTHOR list shows. */
int tab;
/* A handle for the people lists, a post's at:// URI for liked-by and
* reposted-by, so it is sized for the longer of the two. */
char subject[INDIGO_POST_URI_MAX];
Expand Down
2 changes: 1 addition & 1 deletion src/atproto/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ bool indigo_session_submit_search(const char *query, bool paging);
bool indigo_session_submit_post_search(const char *query, bool paging);
/* One person's posts. Shares post search's result array and event, the way the
* people lists share the actor search's. */
bool indigo_session_submit_author_feed(const char *actor, bool paging);
bool indigo_session_submit_author_feed(const char *actor, int tab, bool paging);
/* The signed-in account's curated lists, then one list's members. The members
* reuse the actor search's result array and events, the way the people lists
* do; the lists themselves have their own. */
Expand Down
9 changes: 6 additions & 3 deletions src/atproto/session_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,22 @@
#if defined(__3DS__) && defined(WOLFRAM_3DS)

#include <3ds.h>
#include <wolfram/time.h>
#include <wolfram/3ds.h>
#include <wolfram/actor_prefs_typed.h>
#include <wolfram/actor_typed.h>
#include <wolfram/agent.h>
#include <wolfram/attach.h>
#include <wolfram/oauth_pairing.h>
#include <wolfram/saved_feeds.h>
#include <wolfram/feed_gen_typed.h>
#include <wolfram/list_typed.h>
#include <wolfram/moderation_typed.h>
#include <wolfram/oauth_pairing.h>
#include <wolfram/post_display.h>
#include <wolfram/post_view_typed.h>
#include <wolfram/profile_tab.h>
#include <wolfram/saved_feeds.h>
#include <wolfram/thread_typed.h>
#include <wolfram/threadgate_postgate.h>
#include <wolfram/time.h>

typedef enum {
JOB_NONE = 0,
Expand Down Expand Up @@ -104,6 +105,8 @@ typedef struct {
indigo_graph_action graph;
indigo_search_kind people_kind;
char actor[INDIGO_PROFILE_DID_MAX];
/* JOB_AUTHOR_FEED's wf_profile_tab. */
int tab;
char list_uri[INDIGO_POST_URI_MAX];
/* JOB_FEED's target; the same shape as list_uri, kept separate so the two
* jobs stay readable. */
Expand Down
30 changes: 13 additions & 17 deletions src/atproto/session_read.c
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,8 @@ indigo_session_do_author_feed(const indigo_job *j)
return;
}
memset(&list, 0, sizeof list);
st = wf_agent_get_author_feed_typed(g_session.agent, j->actor, INDIGO_SEARCH_PAGE, cursor,
/* No filter: posts_with_replies would mix
* replies into a person's own posts, and
* replies_filter removes everything but
* them. The bare call is their posts. */
NULL, &list);
st = wf_agent_get_profile_tab_typed(g_session.agent, j->actor, j->tab,
INDIGO_SEARCH_PAGE, cursor, &list);
if (st != WF_OK) {
indigo_session_publish_failure(&ev, "author feed", st);
return;
Expand Down Expand Up @@ -423,17 +419,17 @@ indigo_session_submit_post_search(const char *query, bool paging)
return indigo_session_enqueue(&j);
}

bool
indigo_session_submit_author_feed(const char *actor, bool paging)
{
indigo_job j = {.kind = JOB_AUTHOR_FEED};

if (!actor || !actor[0]) {
return false;
}
j.paging = paging;
indigo_copy_utf8(j.actor, sizeof j.actor, actor);
return indigo_session_enqueue(&j);
bool indigo_session_submit_author_feed(const char *actor, int tab,
bool paging) {
indigo_job j = {.kind = JOB_AUTHOR_FEED};

if (!actor || !actor[0]) {
return false;
}
j.paging = paging;
j.tab = tab;
indigo_copy_utf8(j.actor, sizeof j.actor, actor);
return indigo_session_enqueue(&j);
}

bool
Expand Down
12 changes: 6 additions & 6 deletions src/atproto/session_stub.c
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,12 @@ indigo_session_submit_post_search(const char *query, bool paging)
return false;
}

bool
indigo_session_submit_author_feed(const char *actor, bool paging)
{
(void) actor;
(void) paging;
return false;
bool indigo_session_submit_author_feed(const char *actor, int tab,
bool paging) {
(void)actor;
(void)tab;
(void)paging;
return false;
}

bool
Expand Down
9 changes: 5 additions & 4 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,11 @@ handle_requests(indigo_app *app)
}
break;
case INDIGO_REQUEST_AUTHOR_FEED:
if (!indigo_session_submit_author_feed(app->request_actor,
app->search.cursor[0] != '\0')) {
indigo_app_search_failed(app, "Could not start the request.");
}
if (!indigo_session_submit_author_feed(app->request_actor,
app->search.tab,
app->search.cursor[0] != '\0')) {
indigo_app_search_failed(app, "Could not start the request.");
}
break;
case INDIGO_REQUEST_LISTS:
if (!indigo_session_submit_lists(app->search.cursor[0] != '\0')) {
Expand Down
11 changes: 9 additions & 2 deletions src/ui/layout_bottom.c
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,16 @@ build_bottom_search(const indigo_app *app, indigo_canvas *c)
s->kind == INDIGO_SEARCH_LIKED_BY
? "People who liked this post"
: "People who reposted this post");
} else if (s->kind == INDIGO_SEARCH_AUTHOR) {
/* The box doubles as the tab switch: it names the person and what
* a tap on it will show instead. */
indigo_canvas_text(
c, q.x + 8, q.y + 8, 0.55f, COL_TEXT_SOFT, "@%.26s Tap: %s",
s->subject,
wf_profile_tab_name(indigo_app_author_tab_after(app)));
} else {
indigo_canvas_text(c, q.x + 8, q.y + 8, 0.55f, COL_TEXT_SOFT, "@%.40s",
s->subject);
indigo_canvas_text(c, q.x + 8, q.y + 8, 0.55f, COL_TEXT_SOFT,
"@%.40s", s->subject);
}
}
/* The search screen has no status line in its header to give up, so the
Expand Down
1 change: 1 addition & 0 deletions src/ui/layout_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "util/buildinfo.h"

#include <wolfram/attach.h>
#include <wolfram/profile_tab.h>

#include <stdio.h>
#include <string.h>
Expand Down
30 changes: 30 additions & 0 deletions tests/tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <wolfram/profile_tab.h>

static int s_checks;
static int s_failures;
Expand Down Expand Up @@ -3377,6 +3378,35 @@ test_author_posts(void)
indigo_app_open_author_posts(&app, "someone.else.example");
CHECK(app.search.count == 0);
CHECK(strcmp(app.search.subject, "someone.else.example") == 0);

/* A tap on the header box moves to the next tab and refetches. Someone
* else's tabs skip likes; the signed-in account's do not. */
indigo_app_take_request(&app, &f);
app.search.loading = false;
CHECK(app.search.tab == WF_PROFILE_TAB_POSTS);
indigo_app_next_author_tab(&app);
CHECK(app.search.tab == WF_PROFILE_TAB_REPLIES);
CHECK(strcmp(indigo_search_title(&app.search), "Replies") == 0);
CHECK(app.search.loading);
CHECK(indigo_app_take_request(&app, &f) == INDIGO_REQUEST_AUTHOR_FEED);
app.search.loading = false;
indigo_app_next_author_tab(&app);
CHECK(app.search.tab == WF_PROFILE_TAB_MEDIA);
app.search.loading = false;
indigo_app_next_author_tab(&app);
CHECK(app.search.tab == WF_PROFILE_TAB_POSTS);

snprintf(app.signin.account, sizeof app.signin.account, "Me.Example");
indigo_app_open_author_posts(&app, "me.example");
app.search.tab = WF_PROFILE_TAB_MEDIA;
app.search.loading = false;
indigo_app_next_author_tab(&app);
CHECK(app.search.tab == WF_PROFILE_TAB_LIKES);
CHECK(strcmp(indigo_search_title(&app.search), "Likes") == 0);

/* Opening a person afresh starts on their posts again. */
indigo_app_open_author_posts(&app, "someone.else.example");
CHECK(app.search.tab == WF_PROFILE_TAB_POSTS);
}

/* The pinned post costs no request: getProfile already returned the URI, and
Expand Down
2 changes: 1 addition & 1 deletion wolfram.ref
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.35.0
v0.36.2
Loading