كيفية بناء تطبيق مهام متين باستخدام React وSolid
مقدمة: لماذا نبني تطبيق مهام باستخدام React وSolid؟
في هذا الدليل العملي ستتعلم كيفية إنشاء تطبيق مهام بسيط ولكن قوي باستخدام React وبنية Solid. والمقصود بـSolid هنا ليس مبادئ التصميم الشهيرة SOLID، بل منظومة من الأدوات والاتفاقيات التي تساعد على بناء تطبيقات لامركزية تضع بيانات المستخدم تحت سيطرته الكاملة.
اليوم، تُخزَّن نسبة كبيرة من بياناتنا داخل منصات مركزية مثل Facebook وGoogle. ورغم سهولة هذا النموذج، فإنه يثير تحديات تتعلق بالخصوصية، ويجعل الوصول إلى المحتوى مرتبطاً بمنصة بعينها. فإذا قرر معلمك مشاركة الملفات الدراسية داخل مجموعة على Facebook، فأنت مضطر إلى امتلاك حساب هناك للوصول إليها. وإذا انتقل لاحقاً إلى منصة أخرى، فعليه نقل المستخدمين والبيانات معاً.
أما مع Solid، فالتطبيق والبيانات منفصلان. تبقى البيانات في مكان يختاره المستخدم، ويقوم التطبيق فقط بالقراءة والكتابة فيه. وهذا يعني أن المستخدم يحدد أين تُحفظ بياناته، ومع من يشاركها، وأي تطبيقات يسمح لها بالوصول إليها. ومن جهة المطور، تصبح المنافسة مبنية على جودة التطبيق نفسه، لا على احتكار بيانات المستخدمين.

هذا النموذج مفيد أيضاً لمطوري الواجهة الأمامية، لأنهم يستطيعون بناء تطبيقات تحفظ بيانات المستخدمين دون الحاجة إلى إعداد قاعدة بيانات تقليدية من البداية.
المكتبات المستخدمة في المشروع
سنستخدم في هذا المشروع مكتبتين أساسيتين:
@inrupt/solid-client: للتعامل مع بياناتSolid Podsقراءةً وكتابةً.@inrupt/solid-ui-react: لتوفير مكونات جاهزة تسهّل المصادقة وعرض البيانات داخل تطبيقاتReact.
ويمكنك الرجوع إلى الشيفرة الأصلية عبر المستودع التالي: https://github.com/VirginiaBalseiro/solid-todo-tutorial
كما يمكنك تجربة المشروع على CodeSandbox عبر الرابط: https://codesandbox.io/s/solid-todo-tutorial-7uz4j
المتطلبات الأساسية قبل البدء
- إلمام أساسي بـ
React. - امتلاك حساب
Podضمن منظومةSolid، أو إنشاؤه أثناء عملية تسجيل الدخول.
بدء المشروع باستخدام create-react-app
ابدأ بإنشاء مشروع جديد عبر create-react-app من خلال تنفيذ الأمر التالي:
npx create-react-app solid-todo-tutorial
بعد إنشاء المشروع، انتقل إلى مجلده وثبّت مكتبات Solid المطلوبة:
cd solid-todo-tutorial
npm install @inrupt/solid-client @inrupt/solid-ui-react
مصادقة المستخدم داخل التطبيق
استخدام المكون LoginButton
لكي يتمكن التطبيق من الكتابة داخل Pod الخاص بالمستخدم، يجب أولاً تسجيل الدخول بمستوى الصلاحيات المناسب. توفّر مكتبة solid-ui-react المكون LoginButton الذي يجعل العملية مباشرة وسهلة.
هذا المكون يحتاج إلى خاصيتين أساسيتين:
oidcIssuer: مزود خدمةPod.redirectUrl: الرابط الذي سيعود إليه المستخدم بعد تسجيل الدخول.
كما يمكن تمرير الخاصية الاختيارية authOptions لتحديد اسم التطبيق عبر clientName، وهذا يمنح المستخدم تجربة أوضح عند الموافقة على الصلاحيات.
استبدل محتوى ملف App.js بالشيفرة التالية:
// App.js
import React, { useState } from "react";
import { LoginButton } from "@inrupt/solid-ui-react";
const authOptions = {
clientName: "Solid Todo App",
};
function App() {
const [oidcIssuer, setOidcIssuer] = useState("");
const handleChange = (event) => {
setOidcIssuer(event.target.value);
};
return (
<div className="app-container">
<span>
Log in with:
<input
className="oidc-issuer-input"
type="text"
name="oidcIssuer"
list="providers"
value={oidcIssuer}
onChange={handleChange}
/>
<datalist id="providers">
<option value="https://broker.pod.inrupt.com/" />
<option value="https://inrupt.net/" />
</datalist>
</span>
<LoginButton
oidcIssuer={oidcIssuer}
redirectUrl={window.location.href}
authOptions={authOptions}
/>
</div>
);
}
export default App;
تغليف التطبيق داخل SessionProvider
لكي يتاح لنا استخدام الخطاف useSession في جميع أجزاء التطبيق، يجب تغليف المكون الرئيسي بـSessionProvider داخل ملف index.js:
// index.js
import ReactDOM from "react-dom";
import App from "./App";
import { SessionProvider } from "@inrupt/solid-ui-react";
ReactDOM.render(
<SessionProvider>
<App />
</SessionProvider>,
document.getElementById("root")
);
بعد ذلك شغّل المشروع باستخدام npm start، ثم جرّب تسجيل الدخول. إذا لم يكن لديك حساب، يمكنك إنشاء واحد أثناء العملية.
عرض بيانات الملف الشخصي بعد تسجيل الدخول
بعد نجاح تسجيل الدخول، من الأفضل أن يعرض التطبيق حالة الجلسة واسم المستخدم بدلاً من الاكتفاء بزر الدخول. سنستخدم لهذا الغرض:
useSessionCombinedDataProviderText
يعتمد CombinedDataProvider على عنوان WebID الخاص بالمستخدم لجلب البيانات التعريفية وربطها بالمكونات الأبناء. أما المكون Text فيقرأ خاصية محددة من تلك البيانات ويعرضها مباشرة.
في العادة، يمكن أن يكون اسم المستخدم مخزناً ضمن أحد المعرّفين التاليين:
http://www.w3.org/2006/vcard/ns#fnhttp://xmlns.com/foaf/0.1/name
لذلك من الأفضل تمريرهما معاً عبر الخاصية properties حتى يحاول التطبيق القراءة من الأول، ثم ينتقل إلى الثاني عند الحاجة.
// App.js
import React, { useState } from "react";
import {
LoginButton,
Text,
useSession,
CombinedDataProvider,
} from "@inrupt/solid-ui-react";
const authOptions = {
clientName: "Solid Todo App",
};
function App() {
const { session } = useSession();
const [oidcIssuer, setOidcIssuer] = useState("");
const handleChange = (event) => {
setOidcIssuer(event.target.value);
};
return (
<div className="app-container">
{session.info.isLoggedIn ? (
<CombinedDataProvider
datasetUrl={session.info.webId}
thingUrl={session.info.webId}
>
<div className="message logged-in">
<span>You are logged in as:</span>
<Text
properties={[
"http://www.w3.org/2006/vcard/ns#fn",
"http://xmlns.com/foaf/0.1/name",
]}
/>
</div>
</CombinedDataProvider>
) : (
<div className="message">
<span>You are not logged in.</span>
<span>
Log in with:
<input
className="oidc-issuer-input"
type="text"
name="oidcIssuer"
list="providers"
value={oidcIssuer}
onChange={handleChange}
/>
<datalist id="providers">
<option value="https://broker.pod.inrupt.com/" />
<option value="https://inrupt.net/" />
</datalist>
</span>
<LoginButton
oidcIssuer={oidcIssuer}
redirectUrl={window.location.href}
authOptions={authOptions}
/>
</div>
)}
</div>
);
}
export default App;
إضافة زر تسجيل الخروج
لتوفير تجربة استخدام مكتملة، أضف المكون LogoutButton أسفل اسم المستخدم:
import {
LoginButton,
LogoutButton,
Text,
useSession,
CombinedDataProvider,
} from "@inrupt/solid-ui-react";
// داخل الواجهة
<LogoutButton />
إنشاء مكون لإضافة مهمة جديدة
سنفصل منطق إضافة المهمة في مكون مستقل باسم AddTodo داخل المسار src/components/AddTodo/index.js:
// components/AddTodo/index.js
import React from "react";
function AddTodo() {
return <button className="add-button">Add Todo</button>;
}
export default AddTodo;
ثم نعرض هذا المكون فقط عندما يكون المستخدم مسجلاً للدخول.
تهيئة ملف البيانات الخاص بالمهام
في بنية Solid، تُخزَّن البيانات ضمن dataset يحتوي على مجموعة من العناصر أو Thing. لذا نحتاج أولاً إلى التحقق من وجود ملف المهام، وإذا لم يكن موجوداً نقوم بإنشائه داخل مجلد todos في جذر الـPod.
أنشئ دالة مساعدة داخل الملف src/utils/index.js:
// utils/index.js
import {
createSolidDataset,
getSolidDataset,
saveSolidDatasetAt,
} from "@inrupt/solid-client";
export async function getOrCreateTodoList(containerUri, fetch) {
const indexUrl = `${containerUri}index.ttl`;
try {
const todoList = await getSolidDataset(indexUrl, { fetch });
return todoList;
} catch (error) {
if (error.statusCode === 404) {
const todoList = await saveSolidDatasetAt(
indexUrl,
createSolidDataset(),
{ fetch }
);
return todoList;
}
}
}
تعتمد هذه الدالة على ثلاث وظائف مهمة:
getSolidDataset: لجلب الملف إن كان موجوداً.createSolidDataset: لإنشاء ملف بيانات فارغ في الذاكرة.saveSolidDatasetAt: لحفظ الملف في المسار المحدد.
قراءة عنوان التخزين من ملف المستخدم
قبل إنشاء ملف المهام، يجب معرفة مكان التخزين داخل الـPod. لذلك سنجلب ملف المستخدم الشخصي، ثم نستخرج منه عنوان التخزين باستخدام الخاصية http://www.w3.org/ns/pim/space#storage.
// components/AddTodo/index.js
import { getSolidDataset, getThing, getUrlAll } from "@inrupt/solid-client";
import { useSession } from "@inrupt/solid-ui-react";
import React, { useEffect, useState } from "react";
import { getOrCreateTodoList } from "../../utils";
function AddTodo() {
const { session } = useSession();
const [todoList, setTodoList] = useState();
useEffect(() => {
if (!session) return;
(async () => {
const profileDataset = await getSolidDataset(session.info.webId, {
fetch: session.fetch,
});
const profileThing = getThing(profileDataset, session.info.webId);
const podsUrls = getUrlAll(
profileThing,
"http://www.w3.org/ns/pim/space#storage"
);
const pod = podsUrls[0];
const containerUri = `${pod}todos/`;
const list = await getOrCreateTodoList(containerUri, session.fetch);
setTodoList(list);
})();
}, [session]);
return <button className="add-button">Add Todo</button>;
}
export default AddTodo;
بعد تنفيذ هذه الخطوة، يمكنك التحقق من إنشاء مجلد todos وملف index.ttl باستخدام أداة PodBrowser.


وسيكون محتوى الملف الفارغ مشابهاً لما يلي:
@prefix as: <https://www.w3.org/ns/activitystreams#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix ldp: <http://www.w3.org/ns/ldp#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix acl: <http://www.w3.org/ns/auth/acl#> .
@prefix vcard: <http://www.w3.org/2006/vcard/ns#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix dc: <http://purl.org/dc/terms/> .
@prefix acp: <http://www.w3.org/ns/solid/acp#> .
<https://pod.inrupt.com/virginiabalseiro/todos/index.ttl> rdf:type ldp:RDFSource .
إضافة عنصر جديد إلى قائمة المهام
كل مهمة جديدة ستكون عبارة عن Thing داخل ملف البيانات. سنضيف لكل مهمة ثلاث خصائص:
textتحت المعرّفhttp://schema.org/textcreatedتحت المعرّفhttp://www.w3.org/2002/12/cal/ical#createdtypeتحت المعرّفhttp://www.w3.org/1999/02/22-rdf-syntax-ns#typeبقيمةhttp://www.w3.org/2002/12/cal/ical#Vtodo
فيما يلي نسخة عملية من مكون AddTodo بعد تفعيل الإضافة الفعلية:
// components/AddTodo/index.jsx
import {
addDatetime,
addStringNoLocale,
createThing,
getSourceUrl,
saveSolidDatasetAt,
setThing,
addUrl,
} from "@inrupt/solid-client";
import { useSession } from "@inrupt/solid-ui-react";
import React, { useState } from "react";
const TEXT_PREDICATE = "http://schema.org/text";
const CREATED_PREDICATE = "http://www.w3.org/2002/12/cal/ical#created";
const TODO_CLASS = "http://www.w3.org/2002/12/cal/ical#Vtodo";
const TYPE_PREDICATE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
function AddTodo({ todoList, setTodoList }) {
const { session } = useSession();
const [todoText, setTodoText] = useState("");
const addTodo = async (text) => {
const indexUrl = getSourceUrl(todoList);
const todoWithText = addStringNoLocale(createThing(), TEXT_PREDICATE, text);
const todoWithDate = addDatetime(
todoWithText,
CREATED_PREDICATE,
new Date()
);
const todoWithType = addUrl(todoWithDate, TYPE_PREDICATE, TODO_CLASS);
const updatedTodoList = setThing(todoList, todoWithType);
const updatedDataset = await saveSolidDatasetAt(indexUrl, updatedTodoList, {
fetch: session.fetch,
});
setTodoList(updatedDataset);
};
const handleSubmit = async (event) => {
event.preventDefault();
addTodo(todoText);
setTodoText("");
};
const handleChange = (e) => {
e.preventDefault();
setTodoText(e.target.value);
};
return (
<form className="todo-form" onSubmit={handleSubmit}>
<label htmlFor="todo-input">
<input
id="todo-input"
type="text"
value={todoText}
onChange={handleChange}
/>
</label>
<button className="add-button" type="submit">
Add Todo
</button>
</form>
);
}
export default AddTodo;
إعادة تنظيم الحالة داخل App
لأن كلًّا من AddTodo وTodoList يحتاجان إلى الوصول إلى ملف المهام نفسه، من الأفضل نقل حالة todoList إلى المكون الأعلى App، ثم تمريرها كمُعاملات إلى المكونات الفرعية.
// App.js
import React, { useEffect, useState } from "react";
import {
LoginButton,
LogoutButton,
Text,
useSession,
CombinedDataProvider,
} from "@inrupt/solid-ui-react";
import { getSolidDataset, getUrlAll, getThing } from "@inrupt/solid-client";
import AddTodo from "./components/AddTodo";
import TodoList from "./components/TodoList";
import { getOrCreateTodoList } from "./utils";
const STORAGE_PREDICATE = "http://www.w3.org/ns/pim/space#storage";
const authOptions = {
clientName: "Solid Todo App",
};
function App() {
const { session } = useSession();
const [todoList, setTodoList] = useState();
const [oidcIssuer, setOidcIssuer] = useState("");
const handleChange = (event) => {
setOidcIssuer(event.target.value);
};
useEffect(() => {
if (!session || !session.info.isLoggedIn) return;
(async () => {
const profileDataset = await getSolidDataset(session.info.webId, {
fetch: session.fetch,
});
const profileThing = getThing(profileDataset, session.info.webId);
const podsUrls = getUrlAll(profileThing, STORAGE_PREDICATE);
const pod = podsUrls[0];
const containerUri = `${pod}todos/`;
const list = await getOrCreateTodoList(containerUri, session.fetch);
setTodoList(list);
})();
}, [session, session.info.isLoggedIn]);
return (
<div className="app-container">
{session.info.isLoggedIn ? (
<CombinedDataProvider
datasetUrl={session.info.webId}
thingUrl={session.info.webId}
>
<div className="message logged-in">
<span>You are logged in as:</span>
<Text
properties={[
"http://xmlns.com/foaf/0.1/name",
"http://www.w3.org/2006/vcard/ns#fn",
]}
/>
<LogoutButton />
</div>
<section>
<AddTodo todoList={todoList} setTodoList={setTodoList} />
<TodoList todoList={todoList} setTodoList={setTodoList} />
</section>
</CombinedDataProvider>
) : (
<div className="message">
<span>You are not logged in.</span>
<span>
Log in with:
<input
className="oidc-issuer-input"
type="text"
name="oidcIssuer"
list="providers"
value={oidcIssuer}
onChange={handleChange}
/>
<datalist id="providers">
<option value="https://broker.pod.inrupt.com/" />
<option value="https://inrupt.net/" />
</datalist>
</span>
<LoginButton
oidcIssuer={oidcIssuer}
redirectUrl={window.location.href}
authOptions={authOptions}
/>
</div>
)}
</div>
);
}
export default App;
عرض المهام داخل جدول
لعرض المهام سنستخدم المكونين Table وTableColumn من مكتبة solid-ui-react. كما سنعتمد على الدالة getThingAll لاستخراج جميع العناصر من ملف البيانات.
// components/TodoList/index.jsx
import React from "react";
import { getThingAll, getUrl, getDatetime } from "@inrupt/solid-client";
import { Table, TableColumn } from "@inrupt/solid-ui-react";
const TEXT_PREDICATE = "http://schema.org/text";
const CREATED_PREDICATE = "http://www.w3.org/2002/12/cal/ical#created";
const TODO_CLASS = "http://www.w3.org/2002/12/cal/ical#Vtodo";
const TYPE_PREDICATE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
function TodoList({ todoList }) {
const todoThings = todoList ? getThingAll(todoList) : [];
todoThings.sort((a, b) => {
return getDatetime(a, CREATED_PREDICATE) - getDatetime(b, CREATED_PREDICATE);
});
const thingsArray = todoThings
.filter((t) => getUrl(t, TYPE_PREDICATE) === TODO_CLASS)
.map((t) => {
return { dataset: todoList, thing: t };
});
if (!thingsArray.length) return null;
return (
<div className="table-container">
<span className="tasks-message">
Your to-do list has {thingsArray.length} items
</span>
<Table className="table" things={thingsArray}>
<TableColumn property={TEXT_PREDICATE} header="To Do" sortable />
<TableColumn
property={CREATED_PREDICATE}
dataType="datetime"
header="Created At"
body={({ value }) => value.toDateString()}
sortable
/>
</Table>
</div>
);
}
export default TodoList;
لاحظ أننا قمنا بتصفية العناصر غير الخاصة بالمهام، لأن الخادم يضيف سطراً تقنياً من نوع RDFSource لا يمثل مهمة فعلية.
تمييز المهمة كمكتملة أو غير مكتملة
لإضافة خاصية الإكمال، سنستخدم المعرّف http://www.w3.org/2002/12/cal/ical#completed، وسنخزن داخله تاريخ ووقت الإكمال. عند وضع علامة الصح، نضيف هذه الخاصية، وعند إزالتها نحذفها.
// components/TodoList/index.jsx
import {
addDatetime,
getDatetime,
getSourceUrl,
getThingAll,
getUrl,
removeDatetime,
saveSolidDatasetAt,
setThing,
} from "@inrupt/solid-client";
import { Table, TableColumn, useThing, useSession } from "@inrupt/solid-ui-react";
import React from "react";
const TEXT_PREDICATE = "http://schema.org/text";
const CREATED_PREDICATE = "http://www.w3.org/2002/12/cal/ical#created";
const COMPLETED_PREDICATE = "http://www.w3.org/2002/12/cal/ical#completed";
const TODO_CLASS = "http://www.w3.org/2002/12/cal/ical#Vtodo";
const TYPE_PREDICATE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
function CompletedBody({ checked, handleCheck }) {
const { thing } = useThing();
return (
<label>
<input
type="checkbox"
checked={checked}
onChange={() => handleCheck(thing, checked)}
/>
</label>
);
}
function TodoList({ todoList, setTodoList }) {
const todoThings = todoList ? getThingAll(todoList) : [];
todoThings.sort((a, b) => {
return getDatetime(a, CREATED_PREDICATE) - getDatetime(b, CREATED_PREDICATE);
});
const { fetch } = useSession();
const handleCheck = async (todo, checked) => {
const todosUrl = getSourceUrl(todoList);
let updatedTodos;
if (!checked) {
const date = new Date();
const doneTodo = addDatetime(todo, COMPLETED_PREDICATE, date);
updatedTodos = setThing(todoList, doneTodo, { fetch });
} else {
const date = getDatetime(todo, COMPLETED_PREDICATE);
const undoneTodo = removeDatetime(todo, COMPLETED_PREDICATE, date);
updatedTodos = setThing(todoList, undoneTodo, { fetch });
}
const updatedList = await saveSolidDatasetAt(todosUrl, updatedTodos, {
fetch,
});
setTodoList(updatedList);
};
const thingsArray = todoThings
.filter((t) => getUrl(t, TYPE_PREDICATE) === TODO_CLASS)
.map((t) => {
return { dataset: todoList, thing: t };
});
if (!thingsArray.length) return null;
return (
<div className="table-container">
<span className="tasks-message">
Your to-do list has {thingsArray.length} items
</span>
<Table className="table" things={thingsArray}>
<TableColumn property={TEXT_PREDICATE} header="To Do" sortable />
<TableColumn
property={CREATED_PREDICATE}
dataType="datetime"
header="Created At"
body={({ value }) => value.toDateString()}
sortable
/>
<TableColumn
property={COMPLETED_PREDICATE}
dataType="datetime"
header="Done"
body={({ value }) => (
<CompletedBody
checked={Boolean(value)}
handleCheck={handleCheck}
/>
)}
/>
</Table>
</div>
);
}
export default TodoList;
حذف مهمة من القائمة
لحذف مهمة، نضيف عموداً جديداً يحتوي على زر حذف، ثم نستخدم الدالة removeThing لإزالة العنصر من ملف البيانات وحفظ التغييرات.
// components/TodoList/index.jsx
import {
addDatetime,
getDatetime,
getSourceUrl,
getThingAll,
getUrl,
removeDatetime,
removeThing,
saveSolidDatasetAt,
setThing,
} from "@inrupt/solid-client";
import { Table, TableColumn, useThing, useSession } from "@inrupt/solid-ui-react";
import React from "react";
function DeleteButton({ deleteTodo }) {
const { thing } = useThing();
return (
<button className="delete-button" onClick={() => deleteTodo(thing)}>
Delete
</button>
);
}
function TodoList({ todoList, setTodoList }) {
const { fetch } = useSession();
const deleteTodo = async (todo) => {
const todosUrl = getSourceUrl(todoList);
const updatedTodos = removeThing(todoList, todo);
const updatedDataset = await saveSolidDatasetAt(todosUrl, updatedTodos, {
fetch,
});
setTodoList(updatedDataset);
};
// داخل الجدول
// <TableColumn
// property={TEXT_PREDICATE}
// header=""
// body={() => <DeleteButton deleteTodo={deleteTodo} />}
// />
}
يمكنك إضافة هذا العمود بجوار بقية الأعمدة لتصبح إدارة المهام كاملة: إضافة، عرض، إكمال، وحذف.
ملاحظات مهمة لتحسين جودة التطبيق
ترتيب المهام زمنياً
حتى لا يتغير ترتيب المهام عشوائياً بعد تحديث حالة الإكمال، قم بفرز العناصر اعتماداً على تاريخ الإنشاء باستخدام getDatetime().
تحسين تجربة المستخدم
- أفرغ حقل الإدخال بعد إضافة المهمة باستخدام
setTodoText(""). - أضف رسائل حالة واضحة للمستخدم عند عدم وجود مهام.
- استخدم عناوين مفهومة للأعمدة بدلاً من عرض روابط الخصائص الخام.
قابلية التطوير
هذا المشروع يمثل أساساً ممتازاً لتوسعات لاحقة، مثل:
- إضافة فلاتر لعرض المهام المكتملة أو غير المكتملة فقط.
- إتاحة تعديل نص المهمة.
- إضافة مستويات أولوية وتواريخ استحقاق.
- تقسيم البيانات إلى ملفات متعددة بدلاً من الاعتماد على ملف
index.ttlواحد عند كبر المشروع.
الخلاصة التقنية
يبين هذا التطبيق كيف يمكن دمج React مع منظومة Solid لبناء تجربة حديثة تحترم خصوصية المستخدم وتفصل بين التطبيق والبيانات. تقنياً، الفكرة الأهم هنا ليست مجرد بناء قائمة مهام، بل فهم نموذج جديد لإدارة البيانات يعتمد على Pod وملفات RDF بدلاً من قواعد البيانات المركزية التقليدية. وإذا كنت مطور واجهات أمامية، فتعلم هذا النمط يمنحك قدرة عملية على إنشاء تطبيقات أكثر انفتاحاً ومرونة واستدامة على المدى الطويل.