···191191 expect(card2Result?.cardContent.title).toBeUndefined(); // No metadata provided
192192 });
193193194194- it('should include connected note cards for collection cards', async () => {
194194+ it('should only include notes by collection author, not notes by other users', async () => {
195195 // Create URL card
196196- const url = URL.create('https://example.com/collection-article').unwrap();
196196+ const url = URL.create('https://example.com/shared-article').unwrap();
197197 const urlCard = new CardBuilder()
198198 .withCuratorId(curatorId.value)
199199 .withUrlCard(url)
200200 .buildOrThrow();
201201202202+ urlCard.addToLibrary(curatorId);
203203+202204 await cardRepository.save(urlCard);
203205204204- // Create note card connected to URL card
205205- const noteCard = new CardBuilder()
206206+ // Create note by collection author
207207+ const authorNote = new CardBuilder()
206208 .withCuratorId(curatorId.value)
207207- .withNoteCard(
208208- 'This is my note about the collection article',
209209- 'Collection Note',
210210- )
209209+ .withNoteCard('Note by collection author', 'Author Note')
210210+ .withParentCard(urlCard.cardId)
211211+ .buildOrThrow();
212212+213213+ authorNote.addToLibrary(curatorId);
214214+215215+ await cardRepository.save(authorNote);
216216+217217+ // Create note by different user on the same URL card
218218+ const otherUserNote = new CardBuilder()
219219+ .withCuratorId(otherCuratorId.value)
220220+ .withNoteCard('Note by other user', 'Other User Note')
211221 .withParentCard(urlCard.cardId)
212222 .buildOrThrow();
213223214214- await cardRepository.save(noteCard);
224224+ otherUserNote.addToLibrary(otherCuratorId);
225225+226226+ await cardRepository.save(otherUserNote);
215227216216- // Create collection and add URL card
228228+ // Create collection authored by curatorId and add URL card
217229 const collection = Collection.create(
218230 {
219231 authorId: curatorId,
220220- name: 'Collection with Notes',
232232+ name: 'Collection by First User',
221233 accessType: CollectionAccessType.OPEN,
222234 collaboratorIds: [],
223235 createdAt: new Date(),
···243255 expect(result.items).toHaveLength(1);
244256 const urlCardResult = result.items[0];
245257258258+ // Should only include the note by the collection author, not the other user's note
246259 expect(urlCardResult?.note).toBeDefined();
247247- expect(urlCardResult?.note?.id).toBe(noteCard.cardId.getStringValue());
248248- expect(urlCardResult?.note?.text).toBe(
249249- 'This is my note about the collection article',
260260+ expect(urlCardResult?.note?.id).toBe(authorNote.cardId.getStringValue());
261261+ expect(urlCardResult?.note?.text).toBe('Note by collection author');
262262+ });
263263+264264+ it('should not include notes when only other users have notes, not collection author', async () => {
265265+ // Create URL card
266266+ const url = URL.create(
267267+ 'https://example.com/article-with-other-notes',
268268+ ).unwrap();
269269+ const urlCard = new CardBuilder()
270270+ .withCuratorId(curatorId.value)
271271+ .withUrlCard(url)
272272+ .buildOrThrow();
273273+274274+ urlCard.addToLibrary(curatorId);
275275+276276+ await cardRepository.save(urlCard);
277277+278278+ // Create note by different user on the URL card (NO note by collection author)
279279+ const otherUserNote = new CardBuilder()
280280+ .withCuratorId(otherCuratorId.value)
281281+ .withNoteCard('Note by other user only', 'Other User Note')
282282+ .withParentCard(urlCard.cardId)
283283+ .buildOrThrow();
284284+285285+ otherUserNote.addToLibrary(otherCuratorId);
286286+287287+ await cardRepository.save(otherUserNote);
288288+289289+ // Create collection authored by curatorId and add URL card
290290+ const collection = Collection.create(
291291+ {
292292+ authorId: curatorId,
293293+ name: 'Collection with Other User Notes Only',
294294+ accessType: CollectionAccessType.OPEN,
295295+ collaboratorIds: [],
296296+ createdAt: new Date(),
297297+ updatedAt: new Date(),
298298+ },
299299+ new UniqueEntityID(),
300300+ ).unwrap();
301301+302302+ collection.addCard(urlCard.cardId, curatorId);
303303+ await collectionRepository.save(collection);
304304+305305+ // Query cards in collection
306306+ const result = await queryRepository.getCardsInCollection(
307307+ collection.collectionId.getStringValue(),
308308+ {
309309+ page: 1,
310310+ limit: 10,
311311+ sortBy: CardSortField.UPDATED_AT,
312312+ sortOrder: SortOrder.DESC,
313313+ },
250314 );
315315+316316+ expect(result.items).toHaveLength(1);
317317+ const urlCardResult = result.items[0];
318318+319319+ // Should not include any note since only other users have notes, not the collection author
320320+ expect(urlCardResult?.note).toBeUndefined();
251321 });
252322253323 it('should not include note cards that are not connected to collection URL cards', async () => {
+57-48
src/webapp/app/(auth)/login/page.tsx
···1818import SembleLogo from '@/assets/semble-logo.svg';
1919import { useAuth } from '@/hooks/useAuth';
2020import { useRouter, useSearchParams } from 'next/navigation';
2121+import Link from 'next/link';
21222223function InnerPage() {
2324 const { isAuthenticated, isLoading } = useAuth();
···6667 }
67686869 return (
6969- <Stack gap="xl" maw={300}>
7070- <Stack gap={'xs'}>
7171- <Image
7272- src={SembleLogo.src}
7373- alt="Semble logo"
7474- w={48}
7575- h={64.5}
7676- mx={'auto'}
7777- />
7878- <Title order={1} ta="center">
7979- Welcome back
8080- </Title>
8181- </Stack>
8282- <LoginForm />
8383- <Stack align="center" gap={0}>
8484- <Text fw={500} c={'stone'}>
8585- {"Don't have an account? "}
8686- <Anchor href="/signup" fw={500}>
8787- Sign up
8888- </Anchor>
8989- </Text>
9090- <Popover withArrow shadow="sm">
9191- <PopoverTarget>
9292- <Button
9393- variant="white"
9494- size="md"
9595- fw={500}
9696- fs={'italic'}
9797- c={'stone'}
9898- rightSection={<IoMdHelpCircleOutline size={22} />}
9999- >
100100- How your Cosmik Network account works
101101- </Button>
102102- </PopoverTarget>
103103- <PopoverDropdown>
104104- <Text fw={500} ta="center" maw={380}>
105105- When you sign up today, you’ll create a Bluesky account. In near
106106- future, your account will be seamlessly migrated to our{' '}
107107- <Anchor
108108- href="https://cosmik.network"
109109- target="_blank"
7070+ <Stack gap={'xl'} align="center">
7171+ <Stack gap="xl" maw={300}>
7272+ <Stack gap={'xs'}>
7373+ <Image
7474+ src={SembleLogo.src}
7575+ alt="Semble logo"
7676+ w={48}
7777+ h={64.5}
7878+ mx={'auto'}
7979+ />
8080+ <Title order={1} ta="center">
8181+ Welcome back
8282+ </Title>
8383+ </Stack>
8484+ <LoginForm />
8585+ <Stack align="center" gap={0}>
8686+ <Text fw={500} c={'stone'}>
8787+ {"Don't have an account? "}
8888+ <Anchor href="/signup" fw={500}>
8989+ Sign up
9090+ </Anchor>
9191+ </Text>
9292+ <Popover withArrow shadow="sm">
9393+ <PopoverTarget>
9494+ <Button
9595+ variant="white"
9696+ size="md"
11097 fw={500}
111111- c={'blue'}
9898+ fs={'italic'}
9999+ c={'stone'}
100100+ rightSection={<IoMdHelpCircleOutline size={22} />}
112101 >
113113- Cosmik Network
114114- </Anchor>
115115- .
116116- </Text>
117117- </PopoverDropdown>
118118- </Popover>
102102+ How your Cosmik Network account works
103103+ </Button>
104104+ </PopoverTarget>
105105+ <PopoverDropdown>
106106+ <Text fw={500} ta="center" maw={380}>
107107+ When you sign up today, you’ll create a Bluesky account. In near
108108+ future, your account will be seamlessly migrated to our{' '}
109109+ <Anchor
110110+ href="https://cosmik.network"
111111+ target="_blank"
112112+ fw={500}
113113+ c={'blue'}
114114+ >
115115+ Cosmik Network
116116+ </Anchor>
117117+ .
118118+ </Text>
119119+ </PopoverDropdown>
120120+ </Popover>
121121+ </Stack>
119122 </Stack>
123123+ <Text fw={500} ta={'center'} c={'dark.1'}>
124124+ By continuing, you agree to our{' '}
125125+ <Anchor component={Link} href={'/privacy-policy'} c="dark.2" fw={600}>
126126+ Privacy Policy
127127+ </Anchor>
128128+ </Text>
120129 </Stack>
121130 );
122131}
+50
src/webapp/app/(auth)/privacy-policy/layout.tsx
···11+import {
22+ Container,
33+ Stack,
44+ Image,
55+ Text,
66+ Anchor,
77+ ActionIcon,
88+ Box,
99+ Button,
1010+ Group,
1111+} from '@mantine/core';
1212+import CosmikLogo from '@/assets/cosmik-logo-full.svg';
1313+import SembleLogo from '@/assets/semble-logo.svg';
1414+import { Metadata } from 'next';
1515+import Link from 'next/link';
1616+import { FaBluesky, FaGithub, FaDiscord } from 'react-icons/fa6';
1717+import { RiArrowRightUpLine } from 'react-icons/ri';
1818+1919+export const metadata: Metadata = {
2020+ title: 'Privacy Policy',
2121+ description: `Follow your peers' research trails. Surface and discover new connections. Built on ATProto so you own your data.`,
2222+};
2323+2424+interface Props {
2525+ children: React.ReactNode;
2626+}
2727+2828+export default function Layout(props: Props) {
2929+ return (
3030+ <Container p="md" size="sm">
3131+ <Stack align="start">
3232+ <Anchor component={Link} href={'/'}>
3333+ <Image src={SembleLogo.src} alt="Semble logo" w={'auto'} h={50} />
3434+ </Anchor>
3535+ <Stack>{props.children}</Stack>
3636+ <Box component="footer" px={'md'} py={'xs'} mt={'xl'} mx={'auto'}>
3737+ <Button
3838+ component={Link}
3939+ href="/"
4040+ variant="light"
4141+ color="dark.1"
4242+ fw={600}
4343+ >
4444+ Back to home
4545+ </Button>
4646+ </Box>
4747+ </Stack>
4848+ </Container>
4949+ );
5050+}
+157
src/webapp/app/(auth)/privacy-policy/page.mdx
···11+import { Divider } from '@mantine/core';
22+33+# Privacy Policy
44+55+Last updated: @September 4, 2025
66+77+## Introduction
88+99+Semble (this app is run by Cosmik Network which is fiscally sponsored by [Homeworld.bio](http://Homeworld.bio) (referred to here as "we" or "us" or "Semble").
1010+1111+This Privacy Policy sets out how we collect and use any data you may provide us. By using Semble, you agree to the collection and use of information in accordance with this policy.
1212+1313+Semble is an open-source software available on the [GitHub repo](https://github.com/cosmik-network/semble). Other instances or derivatives hosted elsewhere are not covered by this policy.
1414+1515+## Our Principles
1616+1717+Your privacy is critically important to us. We have a few fundamental principles:
1818+1919+- We collect your personal information only when we need it.
2020+- We don't share your personal information except to comply with the law, develop our products, or protect our rights.
2121+- **We will never sell your data** to third parties.
2222+- We don't store personal information unless required for the on-going operation of our services.
2323+- We make every effort to protect your privacy by using secure technology and carefully choosing trusted third-parties to work with when necessary.
2424+- No spam! We'll only send you communications about Semble-related products and services, and we will make it easy to opt out.
2525+2626+## How We Collect Information
2727+2828+We collect information from you when you connect your ATProto account and when you use our app.
2929+3030+### ATProto Account Connection
3131+3232+When you connect your ATProto account to Semble, we access your profile information including your handle, display name, avatar, and authentication data. This allows us to sync your content and provide our services.
3333+3434+### Content and Activity
3535+3636+We collect and mirror content you create and publish through our app, including posts, media, likes, reposts, follows, and other social activities. This mirrored data helps us provide faster performance and offline access.
3737+3838+### Technical Information
3939+4040+We may retain server logs and collect non-personally-identifying information of the sort that web browsers and servers typically make available, in order to better understand how our users interact with Semble. This includes device information, IP addresses, timestamps, and error logs for debugging and security purposes.
4141+4242+We also use cookies and similar technologies for things like keeping you logged into your account and compiling aggregate data about app usage so that we can offer better experiences and tools in the future.
4343+4444+We may work with trusted third-party service providers to assist us with things like sending notifications, analytics, and app infrastructure. These service providers are not permitted to use the information collected on our behalf except to help us conduct and improve our business.
4545+4646+## How We Use Information
4747+4848+The information we collect from you may be used in one of the following ways:
4949+5050+- **To personalize your experience** — your information helps us to better respond to your individual needs.
5151+- **To improve Semble** — we continually strive to improve based on the information and feedback we receive from you.
5252+- **To improve customer service** — your information helps us to more effectively respond to your requests and support needs.
5353+- **To send periodic communications** — we may use your contact information to send you updates, notifications, and responses to requests or questions.
5454+- **To sync with ATProto** — we mirror your data to provide seamless integration with the ATProto network.
5555+5656+## Important: ATProto Network Considerations
5757+5858+**Please understand**: When you publish content through our app to ATProto, your content becomes part of the decentralized ATProto network. This means:
5959+6060+- Your annotations, cards, and collections are stored on your Personal Data Server (PDS), which may be operated by third parties with their own privacy policies
6161+- Your public content is accessible through the Bluesky firehose and other ATProto indexers
6262+- Other ATProto applications can access your public annotations and collections
6363+- This data distribution is inherent to the ATProto protocol and outside our direct control
6464+6565+We maintain mirrors of your ATProto data on our servers to improve app performance, but the decentralized nature of ATProto means your data exists beyond just our systems.
6666+6767+## How We Protect Your Information
6868+6969+The security of your personal information is important to us, and we do our best to ensure it remains secure. We implement a variety of security measures including encryption, access controls, and regular security audits to maintain the safety of your personal information.
7070+7171+However, we advise you to remember that no method of information transmission or storage is 100% secure. While we strive to do our best to protect your information, we cannot guarantee its absolute security.
7272+7373+## Your Data Rights
7474+7575+At any time, you may request a copy of all your personal data we store, including your account info. You may also request that we correct any inaccurate or incomplete data we have about you, or that we delete all your personal data from our systems. To make any of these requests, please email us and we'll get back to you within 48 hours.
7676+7777+You have several choices available when it comes to information about you:
7878+7979+- **Limit the information that you provide**: you can choose what content to publish and what profile information to share. Please keep in mind that if you limit this information, certain features of Semble may not be accessible.
8080+- **Opt out of communications**: you may opt out of receiving emails from us, or unsubscribe at any time. If you opt out of promotional communications, we may still send you other communications, like those about your account and legal notices.
8181+- **Disconnect your account**: you can disconnect your ATProto account from Semble at any time through app settings. Your data will remain on the ATProto network according to that protocol's specifications.
8282+- **Close your account**: while we'd be sad to see you go, you can close your account if you no longer want to use Semble. Send us a note if you'd like to permanently delete your mirrored data from our servers. Please keep in mind that your data may continue to exist on the ATProto network, and we may retain some information when reasonably needed for compliance with legal obligations or for our legitimate business interests.
8383+8484+## Information Disclosure
8585+8686+We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. **We will never sell your data.** This does not include trusted third parties who assist us in operating our app, conducting our business, or servicing you, so long as those parties agree to keep this information confidential.
8787+8888+We may also release your information when we believe release is appropriate to comply with the law, enforce our policies, or protect ours or others' rights, property, or safety. However, non-personally identifiable information may be provided to other parties for analytics or other uses.
8989+9090+### ATProto and Bluesky Network Sharing
9191+9292+Content you publish through our app is automatically shared across the ATProto network according to the protocol's specifications. In practice, this means your public annotations and collections become accessible through Bluesky's infrastructure and other ATProto applications. This sharing is inherent to how the protocol works and is largely managed by Bluesky's systems.
9393+9494+We may occasionally link to third party products or services from our app. These third party sites have separate and independent privacy policies. We therefore have no responsibility or liability for the content and activities of these linked sites.
9595+9696+## European General Data Protection Regulation (GDPR)
9797+9898+If you are located in a country that falls under the scope of the GDPR, data protection laws give you certain rights with respect to your personal data, subject to any exemptions provided by the law, including the rights to:
9999+100100+- Request access to your personal data
101101+- Request correction or deletion of your personal data
102102+- Object to our use and processing of your personal data
103103+- Request that we limit our use and processing of your personal data
104104+- Request portability of your personal data
105105+106106+You also have the right to make a complaint to a government supervisory authority.
107107+108108+## California Consumer Privacy Act (CCPA)
109109+110110+The California Consumer Privacy Act ("CCPA") requires us to provide California residents with additional information about the categories of personal information we collect and share.
111111+112112+In the last 12 months, we collected the following categories of personal information from California residents:
113113+114114+- Identifiers (like your ATProto handle, contact information, and device identifiers)
115115+- Internet or other electronic network activity information (such as your usage of Semble)
116116+- Geolocation data (such as your location based on your IP address)
117117+- Audio, electronic, visual or similar information (such as content you post)
118118+119119+If you are a California resident, you have additional rights under the CCPA, including the right to:
120120+121121+If you are a California resident, you have additional rights under the CCPA regarding data we directly control, including the right to:
122122+123123+- Request to know what personal information we collect and how we use it (limited to our app's direct data collection)
124124+- Request deletion of personal information we collect or maintain (our mirrored data only; ATProto network data is governed separately)
125125+- Opt out of any sale of personal information (note: we do not sell personal information)
126126+- Not receive discriminatory treatment for exercising your rights
127127+128128+**Note**: These rights apply to data under our direct control. For data stored on ATProto/Bluesky infrastructure, you may need to exercise rights through those platforms directly.
129129+130130+## COPPA
131131+132132+Our app and services are directed to people who are at least 13 years old or older. If you are under the age of 13, per the requirements of COPPA (Children's Online Privacy Protection Act), do not use this app.
133133+134134+## International Users
135135+136136+If you are located outside the US, please note that your information may be transferred to and processed in the US, where our servers are located.
137137+138138+## Your Consent
139139+140140+By using our app, you consent to our privacy policy.
141141+142142+## Changes to This Privacy Policy
143143+144144+Although most changes are likely to be minor, we may change our Privacy Policy from time to time. We encourage users to frequently check this page for any changes. If we make changes, we will notify you by revising the change log below, and, in some cases, we may provide additional notice (like an app notification or email). Your further use of Semble after a change to our Privacy Policy will be subject to the updated policy.
145145+146146+## Contact Us
147147+148148+If you have any questions or concerns, please send an email to [hello@cosmik.network](mailto:hello@cosmik.network) . We take all privacy concerns very seriously and will respond appropriately and quickly.
149149+150150+<Divider />
151151+152152+This document is made available under a Creative Commons BY-SA License. Thanks to [Leaflet](https://leaflet.pub/) for their example.
153153+154154+**Changelog**
155155+156156+September 4, 2025 – Initial draft for review
157157+September 25, 2025 - Initial version published